From 9906737f39904d6440cdc0aff0aa861a5d7eb17e Mon Sep 17 00:00:00 2001 From: Thomas Peterson Date: Wed, 24 Jun 2026 14:53:15 +0200 Subject: [PATCH] Backend Revocation --- .../Controller/DashboardController.php | 5 ++ .../Resources/views/dashboard/index.html.twig | 11 +++ .../PSC/Shop/EntityBundle/Entity/Orderpos.php | 21 ++++++ .../translations/core_order_detail.de.yaml | 4 +- .../views/backend/detail/show.html.twig | 3 +- .../PSC/Shop/OrderBundle/Service/Order.php | 6 ++ .../System/SettingsBundle/Service/Version.php | 70 +++++++++++++++++++ .../Migrations/Version20260624120000.php | 12 ++++ .../data/models/generated/BaseOrderspos.php | 1 + .../bootstrap4_api/layout/default.phtml | 1 + .../templates/revocationform/order.phtml | 2 +- .../vorlagen/tailwindcss/layout/default.phtml | 37 +++++----- .../templates/revocationform/order.phtml | 2 +- .../templates/revocationform/order.phtml | 2 +- .../default/controllers/BasketController.php | 3 + .../controllers/RevocationformController.php | 2 +- 16 files changed, 156 insertions(+), 26 deletions(-) create mode 100644 src/new/src/PSC/System/UpdateBundle/Migrations/Version20260624120000.php diff --git a/src/new/src/PSC/Backend/DashboardBundle/Controller/DashboardController.php b/src/new/src/PSC/Backend/DashboardBundle/Controller/DashboardController.php index 9f2e77776..72a81c23d 100755 --- a/src/new/src/PSC/Backend/DashboardBundle/Controller/DashboardController.php +++ b/src/new/src/PSC/Backend/DashboardBundle/Controller/DashboardController.php @@ -20,6 +20,7 @@ use PSC\Shop\QueueBundle\Service\Queue\Manager; use PSC\System\SettingsBundle\Service\DiskUsage; use PSC\System\SettingsBundle\Service\Instance; use PSC\System\SettingsBundle\Service\Shop; +use PSC\System\SettingsBundle\Service\Version; use PSC\System\UpdateBundle\Service\Migration; use Symfony\Bridge\Twig\Attribute\Template; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -61,6 +62,7 @@ class DashboardController extends AbstractController ContactRepository $contactRepository, Order $orderService, DiskUsage $diskUsage, + Version $version, ) { // Muss vor dem ersten Laden des Shops geprüft werden: ausstehende // Migrationen können Spalten ergänzen, die das Shop-Entity bereits mappt @@ -173,6 +175,9 @@ class DashboardController extends AbstractController 'instance' => $instanceService->getInstance(), 'chart' => $chart, 'diskUsage' => $this->isGranted('ROLE_ADMIN') ? $diskUsage->getUsage() : null, + 'currentVersion' => $version->getRelease(), + 'latestVersion' => $this->isGranted('ROLE_ADMIN') ? $version->getLatestRelease() : null, + 'updateAvailable' => $this->isGranted('ROLE_ADMIN') ? $version->isUpdateAvailable() : false, ]; } } diff --git a/src/new/src/PSC/Backend/DashboardBundle/Resources/views/dashboard/index.html.twig b/src/new/src/PSC/Backend/DashboardBundle/Resources/views/dashboard/index.html.twig index 1efdf8e17..c3d7272e3 100755 --- a/src/new/src/PSC/Backend/DashboardBundle/Resources/views/dashboard/index.html.twig +++ b/src/new/src/PSC/Backend/DashboardBundle/Resources/views/dashboard/index.html.twig @@ -25,6 +25,17 @@ {{ 'psc_backend_dashboard.Layout'|trans }}:{{ shop.layout }} {{ 'psc_backend_dashboard.UID'|trans }}:{{ shop.uid }} {{ 'psc_backend_dashboard.UUID'|trans }}:{{ shop.uuid }} + + Version: + + {{ currentVersion }} + {% if updateAvailable %} + Update verfügbar: {{ latestVersion }} + {% elseif latestVersion %} + Aktuell + {% endif %} + + diff --git a/src/new/src/PSC/Shop/EntityBundle/Entity/Orderpos.php b/src/new/src/PSC/Shop/EntityBundle/Entity/Orderpos.php index 2740c101b..e92bc0da4 100755 --- a/src/new/src/PSC/Shop/EntityBundle/Entity/Orderpos.php +++ b/src/new/src/PSC/Shop/EntityBundle/Entity/Orderpos.php @@ -135,6 +135,14 @@ class Orderpos */ #[ORM\Column(name: 'revoked_at', type: 'datetime', nullable: true)] private ?\DateTimeInterface $revokedAt = null; + + /** + * Snapshot zum Bestellzeitpunkt: War der Widerruf für das Produkt möglich? + * Spätere Änderungen am Produkt-Flag wirken sich dadurch nicht auf + * bestehende Bestellungen aus. + */ + #[ORM\Column(name: 'revoke_allowed', type: 'boolean', nullable: true)] + private ?bool $revokeAllowed = null; /** * @var Product */ @@ -1097,4 +1105,17 @@ class Orderpos { $this->revokedAt = $revokedAt; } + + /** + * War der Widerruf für das Produkt zum Bestellzeitpunkt möglich? + */ + public function isRevokeAllowed(): bool + { + return (bool) $this->revokeAllowed; + } + + public function setRevokeAllowed(?bool $revokeAllowed): void + { + $this->revokeAllowed = $revokeAllowed; + } } diff --git a/src/new/src/PSC/Shop/OrderBundle/Resources/translations/core_order_detail.de.yaml b/src/new/src/PSC/Shop/OrderBundle/Resources/translations/core_order_detail.de.yaml index 2930f8604..b4193d705 100755 --- a/src/new/src/PSC/Shop/OrderBundle/Resources/translations/core_order_detail.de.yaml +++ b/src/new/src/PSC/Shop/OrderBundle/Resources/translations/core_order_detail.de.yaml @@ -60,7 +60,7 @@ prices: Preise actions: Aktionen revokePossible: Widerruf möglich revokeNotPossible: Widerruf nicht möglich -revokeDoneAt: Widerruf erfolgt am +revokeDoneAt: Widerruf erfolgte am revokeBy: durch customerHint: Hinweis für den Kunden printPartnerHint: Hinweis für den Druckpartner @@ -85,4 +85,4 @@ deleteOrderRecovery: Auftrag kann nicht wiederhergestellt werden. deleteOrderRecoveryReally: Auftrag kann nicht wiederhergestellt werden. Dies ist die letzte Möglickeit abzubrechen. close: Schließen deleteOrderReallyReally: Ja wirklich löschen -sendDataToShipping: Daten an Versanddienstleister \ No newline at end of file +sendDataToShipping: Daten an Versanddienstleister diff --git a/src/new/src/PSC/Shop/OrderBundle/Resources/views/backend/detail/show.html.twig b/src/new/src/PSC/Shop/OrderBundle/Resources/views/backend/detail/show.html.twig index ab7b29fe2..cdaa3426b 100755 --- a/src/new/src/PSC/Shop/OrderBundle/Resources/views/backend/detail/show.html.twig +++ b/src/new/src/PSC/Shop/OrderBundle/Resources/views/backend/detail/show.html.twig @@ -555,10 +555,9 @@ {{'revokeDoneAt'|trans}} {% if pos.obj.revokedAt %}{{ pos.obj.revokedAt|date('d.m.Y H:i') }} Uhr{% endif %} - {% if order.contact %}
{{'revokeBy'|trans}} {{ order.contact.firstname }} {{ order.contact.lastname }}{% endif %}
- {% elseif pos.obj.product.revokeButtonEnable %} + {% elseif pos.obj.revokeAllowed %}
{{'revokePossible'|trans}} diff --git a/src/new/src/PSC/Shop/OrderBundle/Service/Order.php b/src/new/src/PSC/Shop/OrderBundle/Service/Order.php index 8a7b394a6..fda51813c 100755 --- a/src/new/src/PSC/Shop/OrderBundle/Service/Order.php +++ b/src/new/src/PSC/Shop/OrderBundle/Service/Order.php @@ -176,6 +176,12 @@ class Order $positionDoc = new \PSC\Shop\EntityBundle\Document\Position(); $positionEntity->setOrder($orderEntity); $this->positionTransformer->toDb($position, $positionEntity, $positionDoc); + // Snapshot zum Bestellzeitpunkt: War der Widerruf für das Produkt + // möglich? Nur bei neu angelegten Positionen setzen, damit spätere + // Produktänderungen bestehende Bestellungen nicht beeinflussen. + if ($positionEntity->getProduct()) { + $positionEntity->setRevokeAllowed($positionEntity->getProduct()->isRevokeButtonEnable()); + } $this->entityManager->persist($positionEntity); $this->entityManager->flush(); diff --git a/src/new/src/PSC/System/SettingsBundle/Service/Version.php b/src/new/src/PSC/System/SettingsBundle/Service/Version.php index 605c5f492..1caf860b9 100644 --- a/src/new/src/PSC/System/SettingsBundle/Service/Version.php +++ b/src/new/src/PSC/System/SettingsBundle/Service/Version.php @@ -4,13 +4,23 @@ namespace PSC\System\SettingsBundle\Service; use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\Yaml\Yaml; +use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Cache\ItemInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; class Version { + /** + * RSS-Feed mit den veröffentlichten Releases. + */ + private const RELEASES_FEED = 'https://git.thomas-peterson.de/boonkerz/printshopcreator/releases.rss'; + private ?array $data = null; public function __construct( private readonly KernelInterface $kernel, + private readonly HttpClientInterface $httpClient, + private readonly CacheInterface $cache, ) {} private function load(): void @@ -37,4 +47,64 @@ class Version $this->load(); return $this->data['changelog'] ?? []; } + + /** + * Neueste verfügbare Version aus dem Release-RSS-Feed. + * Ergebnis wird zwischengespeichert, damit das Dashboard schnell bleibt und + * der Feed nicht bei jedem Aufruf abgefragt wird. Bei Fehlern: null. + */ + public function getLatestRelease(): ?string + { + return $this->cache->get('psc_latest_release_version', function (ItemInterface $item): ?string { + $item->expiresAfter(3600); + + try { + $response = $this->httpClient->request('GET', self::RELEASES_FEED, [ + 'timeout' => 5, + ]); + + $content = $response->getContent(); + $xml = @simplexml_load_string($content); + + if ($xml === false) { + return null; + } + + // RSS 2.0 (channel->item) bzw. Atom (entry) unterstützen. + $title = null; + if (isset($xml->channel->item[0]->title)) { + $title = (string) $xml->channel->item[0]->title; + } elseif (isset($xml->entry[0]->title)) { + $title = (string) $xml->entry[0]->title; + } + + if ($title === null || $title === '') { + return null; + } + + // Versionsnummer (z.B. 2.3.7) aus dem Titel extrahieren. + if (preg_match('/\d+\.\d+(?:\.\d+)*/', $title, $matches)) { + return $matches[0]; + } + + return ltrim(trim($title), 'vV'); + } catch (\Throwable) { + return null; + } + }); + } + + /** + * Ist eine neuere Version als die installierte verfügbar? + */ + public function isUpdateAvailable(): bool + { + $latest = $this->getLatestRelease(); + + if ($latest === null || $latest === '') { + return false; + } + + return version_compare($latest, $this->getRelease(), '>'); + } } diff --git a/src/new/src/PSC/System/UpdateBundle/Migrations/Version20260624120000.php b/src/new/src/PSC/System/UpdateBundle/Migrations/Version20260624120000.php new file mode 100644 index 000000000..0e24862e9 --- /dev/null +++ b/src/new/src/PSC/System/UpdateBundle/Migrations/Version20260624120000.php @@ -0,0 +1,12 @@ +entityManager->getConnection(); + $connection->executeQuery("ALTER TABLE orderspos ADD COLUMN revoke_allowed TINYINT(1) NULL DEFAULT NULL;"); + } +} diff --git a/src/old/application/data/models/generated/BaseOrderspos.php b/src/old/application/data/models/generated/BaseOrderspos.php index 14d493f0d..7a8e0cd3a 100755 --- a/src/old/application/data/models/generated/BaseOrderspos.php +++ b/src/old/application/data/models/generated/BaseOrderspos.php @@ -106,6 +106,7 @@ abstract class BaseOrderspos extends Doctrine_Record $this->hasColumn('revoked', 'boolean', 1); $this->hasColumn('revoked_at', 'timestamp'); + $this->hasColumn('revoke_allowed', 'boolean', 1); } public function setUp() diff --git a/src/old/application/design/vorlagen/bootstrap4_api/layout/default.phtml b/src/old/application/design/vorlagen/bootstrap4_api/layout/default.phtml index eebaf16c6..c43efacf1 100755 --- a/src/old/application/design/vorlagen/bootstrap4_api/layout/default.phtml +++ b/src/old/application/design/vorlagen/bootstrap4_api/layout/default.phtml @@ -330,6 +330,7 @@ if($linkend == "") { ?> designsettings()->get('b2bshop')): ?>
  • translate('AGB')?>
  • translate('Widerrufsbelehrung')?>
  • +
  • translate('Widerruf')?>
  • translate('Datenschutzerklärung')?>
  • translate('Impressum')?>
  • diff --git a/src/old/application/design/vorlagen/bootstrap4_api/templates/revocationform/order.phtml b/src/old/application/design/vorlagen/bootstrap4_api/templates/revocationform/order.phtml index 7bf5da00a..7f046908d 100644 --- a/src/old/application/design/vorlagen/bootstrap4_api/templates/revocationform/order.phtml +++ b/src/old/application/design/vorlagen/bootstrap4_api/templates/revocationform/order.phtml @@ -24,7 +24,7 @@ revoked): ?> translate('Widerrufen am') ?> escape($pos->revoked_at) ?> - Article->revoke_button_enable): ?> + revoke_allowed): ?>
    diff --git a/src/old/application/design/vorlagen/tailwindcss/layout/default.phtml b/src/old/application/design/vorlagen/tailwindcss/layout/default.phtml index d77908f43..e18e6d8b5 100755 --- a/src/old/application/design/vorlagen/tailwindcss/layout/default.phtml +++ b/src/old/application/design/vorlagen/tailwindcss/layout/default.phtml @@ -60,27 +60,25 @@
    -
    - shop->betreiber_company ?> - shop->betreiber_street ?> - shop->betreiber_address ?> -
    -
    -
    @@ -135,6 +133,9 @@ Impressum Datenschutzbestimmungen Wiederrufsbelehrung + designsettings()->get('b2bshop')): ?> + translate('Widerruf')?> +
    diff --git a/src/old/application/design/vorlagen/tailwindcss/templates/revocationform/order.phtml b/src/old/application/design/vorlagen/tailwindcss/templates/revocationform/order.phtml index 91482ee53..f4b65d239 100644 --- a/src/old/application/design/vorlagen/tailwindcss/templates/revocationform/order.phtml +++ b/src/old/application/design/vorlagen/tailwindcss/templates/revocationform/order.phtml @@ -25,7 +25,7 @@ translate('Widerrufen am') ?> escape($pos->revoked_at) ?> - Article->revoke_button_enable): ?> + revoke_allowed): ?> diff --git a/src/old/application/design/vorlagen/tailwindcss_wp/templates/revocationform/order.phtml b/src/old/application/design/vorlagen/tailwindcss_wp/templates/revocationform/order.phtml index 91482ee53..f4b65d239 100644 --- a/src/old/application/design/vorlagen/tailwindcss_wp/templates/revocationform/order.phtml +++ b/src/old/application/design/vorlagen/tailwindcss_wp/templates/revocationform/order.phtml @@ -25,7 +25,7 @@ translate('Widerrufen am') ?> escape($pos->revoked_at) ?> - Article->revoke_button_enable): ?> + revoke_allowed): ?> diff --git a/src/old/application/modules/default/controllers/BasketController.php b/src/old/application/modules/default/controllers/BasketController.php index 3d51ca533..22117a577 100755 --- a/src/old/application/modules/default/controllers/BasketController.php +++ b/src/old/application/modules/default/controllers/BasketController.php @@ -1885,6 +1885,7 @@ class BasketController extends TP_Controller_Action $art->calc_xml = $article->a1_xml; $art->count = $artikel->getCount(); $art->article_id = $article->id; + $art->revoke_allowed = $article->revoke_button_enable ? 1 : 0; $art->priceone = $artikel->getNetto(); $art->priceonesteuer = $artikel->getSteuer(); $art->priceonebrutto = $artikel->getBrutto(); @@ -5174,6 +5175,7 @@ class BasketController extends TP_Controller_Action $art->count = $artikel->getCount(); $art->article_id = $article->id; + $art->revoke_allowed = $article->revoke_button_enable ? 1 : 0; $art->priceone = $artikel->getNetto(); $art->priceonesteuer = $artikel->getSteuer(); $art->priceonebrutto = $artikel->getBrutto(); @@ -5756,6 +5758,7 @@ class BasketController extends TP_Controller_Action $art->orders_id = $order->id; $art->count = $artikel->getCount(); $art->article_id = $article->id; + $art->revoke_allowed = $article->revoke_button_enable ? 1 : 0; $art->priceone = $artikel->getNetto(); $art->shipping_type = $artikel->getShippingtype(); $art->shipping_price = $artikel->getShippingPrice(); diff --git a/src/old/application/modules/default/controllers/RevocationformController.php b/src/old/application/modules/default/controllers/RevocationformController.php index 91772288a..0b15c76f8 100644 --- a/src/old/application/modules/default/controllers/RevocationformController.php +++ b/src/old/application/modules/default/controllers/RevocationformController.php @@ -115,7 +115,7 @@ class RevocationformController extends TP_Controller_Action ->where('p.uuid = ? AND p.orders_id = ?', array($this->_getParam('pos'), $order->id)) ->fetchOne(); - if ($pos && $pos->Article && $pos->Article->revoke_button_enable && !$pos->revoked) { + if ($pos && $pos->revoke_allowed && !$pos->revoked) { $pos->revoked = 1; $pos->revoked_at = date('Y-m-d H:i:s'); $pos->save();