From 51061f16998c5946788748b2dbd9ef9eceee46ea Mon Sep 17 00:00:00 2001 From: Thomas Peterson Date: Tue, 23 Jun 2026 10:34:00 +0200 Subject: [PATCH] Backend Revocaton --- .../PSC/Shop/EntityBundle/Entity/Orderpos.php | 38 +++ .../PSC/Shop/EntityBundle/Entity/Product.php | 29 ++ .../translations/core_order_detail.de.yaml | 15 + .../translations/core_order_detail.en.yaml | 15 + .../views/backend/detail/show.html.twig | 263 ++++++++++-------- .../Form/Backend/Product/ProductType.php | 1 + .../translations/core_product_edit.de.yaml | 1 + .../translations/core_product_edit.en.yaml | 1 + .../backend/product/edit/create.html.twig | 3 + .../views/backend/product/edit/edit.html.twig | 3 + .../Migrations/Version20260622120000.php | 12 + .../Migrations/Version20260622140000.php | 13 + src/new/var/tailwind/backend.built.css | 61 +++- 13 files changed, 342 insertions(+), 113 deletions(-) create mode 100644 src/new/src/PSC/System/UpdateBundle/Migrations/Version20260622120000.php create mode 100644 src/new/src/PSC/System/UpdateBundle/Migrations/Version20260622140000.php diff --git a/src/new/src/PSC/Shop/EntityBundle/Entity/Orderpos.php b/src/new/src/PSC/Shop/EntityBundle/Entity/Orderpos.php index 75d598d52..2740c101b 100755 --- a/src/new/src/PSC/Shop/EntityBundle/Entity/Orderpos.php +++ b/src/new/src/PSC/Shop/EntityBundle/Entity/Orderpos.php @@ -123,6 +123,18 @@ class Orderpos #[ORM\Column(name: 'external_approval_message', type: 'text', nullable: true)] private ?string $externalApprovalMessage = null; + + /** + * Wurde die Position vom Kunden widerrufen? + */ + #[ORM\Column(name: 'revoked', type: 'boolean', nullable: true)] + private ?bool $revoked = null; + + /** + * Zeitpunkt des Widerrufs durch den Kunden. + */ + #[ORM\Column(name: 'revoked_at', type: 'datetime', nullable: true)] + private ?\DateTimeInterface $revokedAt = null; /** * @var Product */ @@ -1059,4 +1071,30 @@ class Orderpos { $this->externalApprovalMessage = $message; } + + /** + * Wurde die Position vom Kunden widerrufen? + */ + public function isRevoked(): bool + { + return (bool) $this->revoked; + } + + public function setRevoked(?bool $revoked): void + { + $this->revoked = $revoked; + } + + /** + * Zeitpunkt des Widerrufs. + */ + public function getRevokedAt(): ?\DateTimeInterface + { + return $this->revokedAt; + } + + public function setRevokedAt(?\DateTimeInterface $revokedAt): void + { + $this->revokedAt = $revokedAt; + } } diff --git a/src/new/src/PSC/Shop/EntityBundle/Entity/Product.php b/src/new/src/PSC/Shop/EntityBundle/Entity/Product.php index 716993411..1fc888f69 100755 --- a/src/new/src/PSC/Shop/EntityBundle/Entity/Product.php +++ b/src/new/src/PSC/Shop/EntityBundle/Entity/Product.php @@ -584,6 +584,14 @@ class Product #[ORM\Column(name: 'not_buy', type: 'boolean')] protected $notBuy; + /** + * Widerrufsbutton im Bestellablauf aktivieren + * + * @var boolean + */ + #[ORM\Column(name: 'revoke_button_enable', type: 'boolean', nullable: true)] + protected $revokeButtonEnable; + /** * Packageformat * @@ -907,6 +915,7 @@ class Product { $this->enable = false; $this->notBuy = false; + $this->revokeButtonEnable = false; $this->private = false; $this->displayNotInOverview = false; $this->uuid = UUID::uuid4(); @@ -1083,6 +1092,26 @@ class Product $this->notBuy = $notBuy; } + /** + * Ist der Widerrufsbutton für dieses Produkt aktiviert? + * + * @return boolean + */ + public function isRevokeButtonEnable() + { + return (bool) $this->revokeButtonEnable; + } + + /** + * Setzt, ob der Widerrufsbutton für dieses Produkt aktiviert ist + * + * @param boolean $revokeButtonEnable + */ + public function setRevokeButtonEnable($revokeButtonEnable) + { + $this->revokeButtonEnable = $revokeButtonEnable; + } + /** * Ist das Produkt privatisiert? * 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 005fbd4a4..2930f8604 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 @@ -52,6 +52,21 @@ brutto: Brutto Preis vat: MwSt. positions: Positionen +and: und +nameDetails: Name / Details +propertiesEdition: Eigenschaften / Auflage +revokeStatus: Widerruf / Status +prices: Preise +actions: Aktionen +revokePossible: Widerruf möglich +revokeNotPossible: Widerruf nicht möglich +revokeDoneAt: Widerruf erfolgt am +revokeBy: durch +customerHint: Hinweis für den Kunden +printPartnerHint: Hinweis für den Druckpartner +notifyPrintPartner: Printpartner benachrichtigen +showCustomerInAccount: Dem Kunden in seinem Account anzeigen +label: Label addresses: Adressen billingaddress: Rechnungsanschrift shippingaddress: Lieferanschrift diff --git a/src/new/src/PSC/Shop/OrderBundle/Resources/translations/core_order_detail.en.yaml b/src/new/src/PSC/Shop/OrderBundle/Resources/translations/core_order_detail.en.yaml index 4e3412756..353e279b5 100755 --- a/src/new/src/PSC/Shop/OrderBundle/Resources/translations/core_order_detail.en.yaml +++ b/src/new/src/PSC/Shop/OrderBundle/Resources/translations/core_order_detail.en.yaml @@ -49,6 +49,21 @@ brutto: Gross Price vat: Vat positions: Positions +and: and +nameDetails: Name / Details +propertiesEdition: Properties / Quantity +revokeStatus: Revocation / Status +prices: Prices +actions: Actions +revokePossible: Revocation possible +revokeNotPossible: Revocation not possible +revokeDoneAt: Revoked on +revokeBy: by +customerHint: Note for the customer +printPartnerHint: Note for the print partner +notifyPrintPartner: Notify print partner +showCustomerInAccount: Show to customer in their account +label: Label addresses: Addresses billingaddress: Billing Address shippingaddress: Shipping Address 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 39b67d42a..ab7b29fe2 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 @@ -433,79 +433,103 @@ - - - - - - - - - - + + + + + + + {% for pos in positions %} {% set posModel = orderModel.getPositionByUuid(pos.obj.uuid) %} - - - - - - - + + {# Pos #} + + + {# Name / Details #} + - - - - - - - - - - - - + + {# Preise #} + + + {# Aktionen #} + - - - - {% if pos.obj.uploads|length > 0 %} - - - - {% endif %} - - - {% endfor %}
{{'Pos'|trans}}{{'name'|trans}}{{'wkpos1'|trans}}{{'wkpos2'|trans}}{{'properties'|trans}}{{'status'|trans}}{{'netto'|trans}}{{'vat'|trans}}{{'brutto'|trans}}{{'Pos'|trans}}{{'nameDetails'|trans}}{{'wkpos1'|trans}} {{'and'|trans}} {{'wkpos2'|trans}}{{'propertiesEdition'|trans}}{{'revokeStatus'|trans}}{{'prices'|trans}}{{'actions'|trans}}
{{ pos.obj.pos }} - {{ pos.obj.product.title }} -
ArtNr intern: {{ pos.obj.product.nrIntern }}
-
{{ 'weight'|trans }}: {{ pos.obj.weight }}
-
{{ pos.obj.basketfield1 }}{{ pos.obj.basketfield2 }} -
- -
{{ pos.obj.pos }} + {{ pos.obj.product.title }} +
ArtNr intern: {{ pos.obj.product.nrIntern }}
+
{{ 'weight'|trans }}: {{ pos.obj.weight }} g
+ + {% if pos.objDoc.setConfig|length > 0 %} +
+

Set bestehend aus:

+
    + {% for item in pos.objDoc.setConfig %} + {% if (item.article_id|product) %} +
  • {{ (item.article_id|product).title }} ({% if item.count == 0 %}{{ pos.count }}{% else %}{{ pos.count * item.count }}{% endif %})
  • + {% endif %} + {% endfor %} +
+ {% endif %} + + {# Vorschaubilder über denselben Mechanismus wie in der Auftragsliste: + gated über das UploadTypeObject (canPreview) und die Position-Model-Uploads. + Andernfalls – identisch zur Liste – der "not available"-Platzhalter. #} + {% set uploadType = posModel.uploadTypeObject %} +
+ {% if uploadType and uploadType.canPreview and posModel.uploads|length > 0 %} +
+ {% for upload in posModel.uploads %} +
+ + + {{ upload.fileName }} +
+ {% endfor %} +
+ {% else %} +
+ + + + not available +
+ {% endif %} +
+ +
+ {% if shop.docLabelPosition != "" %}{{'label'|trans}}{% endif %} + {% if shop.docJobticketPosition != "" %}{{'jobticket'|trans}}{% endif %} + {% if pos.obj.layouterMode == 4 and pos.objDoc %} + + + Layouter öffnen + + {% endif %}
{{ pos.obj.priceAllNetto|number_format(2, ',', '.') }}€{{ pos.obj.priceAllSteuer|number_format(2, ',', '.') }}€{{ pos.obj.priceAllBrutto|number_format(2, ',', '.') }}€ - {% if pos.obj.layouterMode == 4 and pos.objDoc %} - - - Layouter öffnen - + + {# WK_Pos_1 / WK_Pos_2 – Hinweise #} + + {% if pos.obj.basketfield1 %} +

{{'customerHint'|trans}}

+

{{ pos.obj.basketfield1 }}

+ {% endif %} + {% if pos.obj.basketfield2 %} +

{{'printPartnerHint'|trans}}

+

{{ pos.obj.basketfield2 }}

{% endif %}
- {% if pos.objDoc.setConfig|length > 0 %} -

Set bestehend aus:

-
    - {% for item in pos.objDoc.setConfig %} - {% if (item.article_id|product) %} -
  • {{ (item.article_id|product).title }} ({% if item.count == 0 %}{{ pos.count }}{% else %}{{ pos.count * item.count }}{% endif %})
  • - {% endif %} - {% endfor %} -
- {% endif %} -
+ + {# Eigenschaften / Auflage #} + {% if pos.objCalc %} -
    +
      {% for opt in pos.objCalc.getOptions %} {% if opt is not instanceof('\\PSC\\Library\\Calc\\Option\\Type\\Hidden') and opt.isValid() %}
    • {{ opt.name }}: {{ opt.value }}
    • @@ -522,20 +546,80 @@ {% endif %} {% endfor %}
-
-
+ + {# Widerruf / Status #} +
+ {% if pos.obj.revoked %} +
+ + + {{'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 %} +
+ + {{'revokePossible'|trans}} +
+ {% else %} +
+ + {{'revokeNotPossible'|trans}} +
+ {% endif %} + +
+ + +
+
+
+
+
{{'netto'|trans}}
+
{{ pos.obj.priceAllNetto|number_format(2, ',', '.') }} €
+
+
+
{{'vat'|trans}}
+
{{ pos.obj.priceAllSteuer|number_format(2, ',', '.') }} €
+
+
+
{{'brutto'|trans}}
+
{{ pos.obj.priceAllBrutto|number_format(2, ',', '.') }} €
+
+
+
+
+
{{ form_start(pos.formProd) }} -
{{ form_widget(pos.formProd.printPartnerEmail) }}
+ + {{ form_widget(pos.formProd.printPartnerEmail) }} {{ form_rest(pos.formProd) }} - {{ form_end(pos.formProd) }}
-
+
{{ form_start(pos.formProdinfo) }} -
{{ form_widget(pos.formProdinfo.customerInfo) }}
+ + {{ form_widget(pos.formProdinfo.customerInfo) }} {{ form_rest(pos.formProdinfo) }} - {{ form_end(pos.formProdinfo) }}
{% if posModel.reOrder %} @@ -545,45 +629,6 @@
-
- {% if shop.docOfferPosition != "" %}{{'offer'|trans}}{% endif %} - {% if shop.docOrderPosition != "" %}{{'order'|trans}}{% endif %} - {% if shop.docInvoicePosition != "" %}{{'invoice'|trans}}{% endif %} - {% if shop.docDeliveryPosition != "" %}{{'deliverynote'|trans}}{% endif %} - {% if shop.docLabelPosition != "" %}{{'labels'|trans}}{% endif %} - {% if shop.docJobticketPosition != "" %}{{'jobticket'|trans}}{% endif %} - {% if shop.docStornoPosition != "" %}{{'cancel'|trans}}{% endif %} -
-
-
- {% for upload in pos.obj.uploads %} -
- - - {{ upload.name }} -
- {% endfor %} -
-
diff --git a/src/new/src/PSC/Shop/ProductBundle/Form/Backend/Product/ProductType.php b/src/new/src/PSC/Shop/ProductBundle/Form/Backend/Product/ProductType.php index 6b0c68fb5..b2f09e699 100755 --- a/src/new/src/PSC/Shop/ProductBundle/Form/Backend/Product/ProductType.php +++ b/src/new/src/PSC/Shop/ProductBundle/Form/Backend/Product/ProductType.php @@ -94,6 +94,7 @@ class ProductType extends AbstractType ->add('displayNoPrice', CheckboxType::class, ['required' => false, 'label' => 'notpriceshow']) ->add('displayNotInOverview', CheckboxType::class, ['required' => false, 'label' => 'notshow']) ->add('notBuy', CheckboxType::class, ['required' => false, 'label' => 'notshoworderbtn']) + ->add('revokeButtonEnable', CheckboxType::class, ['required' => false, 'label' => 'enableRevokeButton']) ->add('notEdit', CheckboxType::class, ['required' => false, 'label' => 'Sellasproduct']) ->add('url', TextType::class, ['required' => true, 'label' => 'Url']) ->add('aribaUNSPSC', TextType::class, ['required' => false, 'label' => 'Ariba UNSPSC Identifikation']) diff --git a/src/new/src/PSC/Shop/ProductBundle/Resources/translations/core_product_edit.de.yaml b/src/new/src/PSC/Shop/ProductBundle/Resources/translations/core_product_edit.de.yaml index c0d20e05a..066b09035 100755 --- a/src/new/src/PSC/Shop/ProductBundle/Resources/translations/core_product_edit.de.yaml +++ b/src/new/src/PSC/Shop/ProductBundle/Resources/translations/core_product_edit.de.yaml @@ -37,6 +37,7 @@ Extended: extended Private: Privat notshow: Nicht im Shop anzeigen notshoworderbtn: „Bestellen“ Button nicht anzeigen +enableRevokeButton: Widerrufsbutton aktivieren Sellasproduct: Als Produkt verkaufen notpriceshow: Preis nicht anzeigen pricenull: Bestellbar mit Preis 0€ diff --git a/src/new/src/PSC/Shop/ProductBundle/Resources/translations/core_product_edit.en.yaml b/src/new/src/PSC/Shop/ProductBundle/Resources/translations/core_product_edit.en.yaml index 29ee8cf2c..a9975f213 100755 --- a/src/new/src/PSC/Shop/ProductBundle/Resources/translations/core_product_edit.en.yaml +++ b/src/new/src/PSC/Shop/ProductBundle/Resources/translations/core_product_edit.en.yaml @@ -36,6 +36,7 @@ Extended: Extended Private: Private notshow: Do not show in the shop notshoworderbtn: Do not show the „Order" button +enableRevokeButton: Enable revocation button Sellasproduct: Sell ​​as a product notpriceshow: Don't show price pricenull: Can be ordered with a price of € 0 diff --git a/src/new/src/PSC/Shop/ProductBundle/Resources/views/backend/product/edit/create.html.twig b/src/new/src/PSC/Shop/ProductBundle/Resources/views/backend/product/edit/create.html.twig index 6dfb8b1b0..64e327830 100755 --- a/src/new/src/PSC/Shop/ProductBundle/Resources/views/backend/product/edit/create.html.twig +++ b/src/new/src/PSC/Shop/ProductBundle/Resources/views/backend/product/edit/create.html.twig @@ -190,6 +190,9 @@
{{ form_widget(form.asRequest) }}
+
+ {{ form_widget(form.revokeButtonEnable) }} +
diff --git a/src/new/src/PSC/Shop/ProductBundle/Resources/views/backend/product/edit/edit.html.twig b/src/new/src/PSC/Shop/ProductBundle/Resources/views/backend/product/edit/edit.html.twig index d3504f41d..a1822954e 100755 --- a/src/new/src/PSC/Shop/ProductBundle/Resources/views/backend/product/edit/edit.html.twig +++ b/src/new/src/PSC/Shop/ProductBundle/Resources/views/backend/product/edit/edit.html.twig @@ -209,6 +209,9 @@ a[href^="#formlayouter"] {display:none;}
{{ form_widget(form.asRequest) }}
+
+ {{ form_widget(form.revokeButtonEnable) }} +
diff --git a/src/new/src/PSC/System/UpdateBundle/Migrations/Version20260622120000.php b/src/new/src/PSC/System/UpdateBundle/Migrations/Version20260622120000.php new file mode 100644 index 000000000..1dac0eee3 --- /dev/null +++ b/src/new/src/PSC/System/UpdateBundle/Migrations/Version20260622120000.php @@ -0,0 +1,12 @@ +entityManager->getConnection(); + $connection->executeQuery("ALTER TABLE article ADD COLUMN revoke_button_enable TINYINT(1) NULL DEFAULT NULL;"); + } +} diff --git a/src/new/src/PSC/System/UpdateBundle/Migrations/Version20260622140000.php b/src/new/src/PSC/System/UpdateBundle/Migrations/Version20260622140000.php new file mode 100644 index 000000000..755dd1c38 --- /dev/null +++ b/src/new/src/PSC/System/UpdateBundle/Migrations/Version20260622140000.php @@ -0,0 +1,13 @@ +entityManager->getConnection(); + $connection->executeQuery("ALTER TABLE orderspos ADD COLUMN revoked TINYINT(1) NULL DEFAULT NULL;"); + $connection->executeQuery("ALTER TABLE orderspos ADD COLUMN revoked_at DATETIME NULL DEFAULT NULL;"); + } +} diff --git a/src/new/var/tailwind/backend.built.css b/src/new/var/tailwind/backend.built.css index 9f1319538..c8542a26c 100644 --- a/src/new/var/tailwind/backend.built.css +++ b/src/new/var/tailwind/backend.built.css @@ -982,6 +982,10 @@ html { margin-bottom: 0px; } +.mb-0\.5{ + margin-bottom: 0.125rem; +} + .mb-1{ margin-bottom: 0.25rem; } @@ -1206,6 +1210,10 @@ html { height: 0.5rem; } +.h-24{ + height: 6rem; +} + .h-28{ height: 7rem; } @@ -1326,6 +1334,10 @@ html { width: 1rem; } +.w-40{ + width: 10rem; +} + .w-48{ width: 12rem; } @@ -1587,10 +1599,6 @@ html { grid-template-columns: repeat(2, minmax(0, 1fr)); } -.grid-cols-3{ - grid-template-columns: repeat(3, minmax(0, 1fr)); -} - .grid-cols-4{ grid-template-columns: repeat(4, minmax(0, 1fr)); } @@ -1923,6 +1931,11 @@ html { border-color: rgb(29 78 216 / var(--tw-border-opacity)); } +.border-emerald-200{ + --tw-border-opacity: 1; + border-color: rgb(167 243 208 / var(--tw-border-opacity)); +} + .border-gray-100{ --tw-border-opacity: 1; border-color: rgb(243 244 246 / var(--tw-border-opacity)); @@ -1963,6 +1976,16 @@ html { border-color: rgb(239 68 68 / var(--tw-border-opacity)); } +.border-rose-200{ + --tw-border-opacity: 1; + border-color: rgb(254 205 211 / var(--tw-border-opacity)); +} + +.border-sky-200{ + --tw-border-opacity: 1; + border-color: rgb(186 230 253 / var(--tw-border-opacity)); +} + .border-stone-200{ --tw-border-opacity: 1; border-color: rgb(231 229 228 / var(--tw-border-opacity)); @@ -2012,6 +2035,11 @@ html { background-color: rgb(29 78 216 / var(--tw-bg-opacity)); } +.bg-emerald-50{ + --tw-bg-opacity: 1; + background-color: rgb(236 253 245 / var(--tw-bg-opacity)); +} + .bg-emerald-500{ --tw-bg-opacity: 1; background-color: rgb(16 185 129 / var(--tw-bg-opacity)); @@ -2124,11 +2152,21 @@ html { background-color: rgb(220 38 38 / var(--tw-bg-opacity)); } +.bg-rose-50{ + --tw-bg-opacity: 1; + background-color: rgb(255 241 242 / var(--tw-bg-opacity)); +} + .bg-rose-500{ --tw-bg-opacity: 1; background-color: rgb(244 63 94 / var(--tw-bg-opacity)); } +.bg-sky-50{ + --tw-bg-opacity: 1; + background-color: rgb(240 249 255 / var(--tw-bg-opacity)); +} + .bg-sky-500{ --tw-bg-opacity: 1; background-color: rgb(14 165 233 / var(--tw-bg-opacity)); @@ -2495,6 +2533,11 @@ html { color: rgb(5 150 105 / var(--tw-text-opacity)); } +.text-emerald-700{ + --tw-text-opacity: 1; + color: rgb(4 120 87 / var(--tw-text-opacity)); +} + .text-gray-400{ --tw-text-opacity: 1; color: rgb(156 163 175 / var(--tw-text-opacity)); @@ -2595,6 +2638,16 @@ html { color: rgb(127 29 29 / var(--tw-text-opacity)); } +.text-rose-700{ + --tw-text-opacity: 1; + color: rgb(190 18 60 / var(--tw-text-opacity)); +} + +.text-sky-800{ + --tw-text-opacity: 1; + color: rgb(7 89 133 / var(--tw-text-opacity)); +} + .text-stone-500{ --tw-text-opacity: 1; color: rgb(120 113 108 / var(--tw-text-opacity));