Neutraler Versand

Hi, ich finde absolut nichts zu dem Thema und hoffe, dass mir hier jemand helfen kann.

Wir wollen neutralen Versand ermöglichen. Das heißt, der Kunde kann selbst die Absender-Adresse bestimmen.

Da ich wie gesagt nichts gefunden habe, wollte ich versuchen das selbst zu machen. Wäre natürlich toll, es gäbe ein Plugin dafür. Aber ich finde keines.

Nach einer Mischung aus ChatGPT befragen, Dokumentation lesen und Quellcode durchstöbern, ist das nachfolgende mein aktueller Stand:

Es ist im Prinzip nichts anderes als eine Kopie der Versand-Adresse. Deshalb habe ich das Feld im Shopware Admin kopiert:

<!-- custom/plugins/MyPlugin/src/Resources/app/administration/src/module/sw-order/sw-order-detail-details/sw-order-detail-details.html.twig -->
{% block sw_order_detail_details_shipping_address %}
    <sw-order-address-selection
        class="sw-order-detail-details__sender-address"
        type="sender"
        :address="senderAddress"
        :address-id="selectedSenderAddressId"
        :disabled="!acl.can('order.editor')"
        :label="$tc('sw-order.createBase.detailsBody.labelSenderAddress')"
        @change-address="onChangeOrderAddress"
    />

    {% parent() %}
{% endblock %}
// custom/plugins/MyPlugin/src/Resources/app/administration/src/module/sw-order/sw-order-detail-details/index.js
const { Component } = Shopware;
import template from "./sw-order-detail-details.html.twig";

Component.override("sw-order-detail-details", {
  template,
  computed: {
    senderAddress() {
      return this.delivery.senderOrderAddress;
    },
    selectedSenderAddressId() {
      const currentAddress = this.orderAddressIds.find(
        (item) => item.type === "sender"
      );
      return currentAddress?.customerAddressId || this.senderAddress.id;
    },
  },
});

Die obligatorische Registrierung in main.js habe ich natürlich vorgenommen. Das klappt auch soweit: Das Feld wird angezeigt und ich kann eine der verfügbaren Kunden-Adressen auswählen.

Als nächstes habe ich eine neue Migration erstellt – anhand der ursprünglichen Migration für shipping_order_address_id:

<?php declare(strict_types=1);
// custom/plugins/MyPlugin/src/Migration/Migration[timestamp]MyPlugin.php

namespace MyPlugin\Migration;

use Doctrine\DBAL\Connection;
use Shopware\Core\Framework\Migration\MigrationStep;

class Migration1[timestamp]MyPlugin extends MigrationStep
{
    public function getCreationTimestamp(): int
    {
        return [timestamp];
    }

    public function update(Connection $connection): void
    {
        $connection->executeStatement('
            ALTER TABLE `order_delivery`
                ADD COLUMN `sender_order_address_id` BINARY(16) NULL,
                ADD CONSTRAINT `fk.order_delivery.sender_order_address_id` FOREIGN KEY (`sender_order_address_id`)
                    REFERENCES `order_address` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
        ');
    }

    public function updateDestructive(Connection $connection): void
    {
        // implement update destructive
    }
}

Das klappt auch. Nach der Migration ist die neue Tabellen-Spalte in der Datenbank verfügbar.

Weiter geht es mit den EntityDefinitionExtensions – im Prinzip mache ich da auch nur das, was mit shipping_order_address_id passiert:

<?php declare(strict_types=1);
// custom/plugins/MyPlugin/src/Extension/Checkout/Order/Aggregate/OrderDelivery/OrderDeliveryExtension.php

namespace MyPlugin\Extension\Checkout\Order\Aggregate\OrderDelivery;

use Shopware\Core\Checkout\Order\Aggregate\OrderDelivery\OrderDeliveryDefinition;
use Shopware\Core\Checkout\Order\Aggregate\OrderAddress\OrderAddressDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\Field\FkField;
use Shopware\Core\Framework\DataAbstractionLayer\EntityExtension;
use Shopware\Core\Framework\DataAbstractionLayer\FieldCollection;
use Shopware\Core\Framework\DataAbstractionLayer\Field\ManyToOneAssociationField;
use Shopware\Core\Framework\Feature;
use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\ApiAware;
use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\SearchRanking;

class OrderDeliveryExtension extends EntityExtension
{
    public function extendFields(FieldCollection $collection): void
    {
        $autoload = !Feature::isActive('v6.6.0.0');

        $collection->add(
            (new FkField('sender_order_address_id', 'senderOrderAddressId', OrderAddressDefinition::class))->addFlags(new ApiAware())
        );

        // Brauche ich das?
        $collection->add(
            (new ManyToOneAssociationField('senderOrderAddress', 'sender_order_address_id', OrderAddressDefinition::class, 'id', $autoload))->addFlags(new ApiAware(), new SearchRanking(SearchRanking::ASSOCIATION_SEARCH_RANKING))
        );
    }

    public function getDefinitionClass(): string
    {
        return OrderDeliveryDefinition::class;
    }
}

Jetzt taucht senderOrderAddressId im Objekt this.delivery (siehe senderAddress() in der index.js) auf.

Bei der nachfolgenden Extension bin ich mir nicht sicher: Brauche ich das?

<?php declare(strict_types=1);
// custom/plugins/MyPlugin/src/Extension/Checkout/Order/Aggregate/OrderAddress/OrderAddressExtension.php

namespace MyPlugin\Extension\Checkout\Order\Aggregate\OrderAddress;

use Shopware\Core\Checkout\Order\Aggregate\OrderDelivery\OrderDeliveryDefinition;
use Shopware\Core\Checkout\Order\Aggregate\OrderAddress\OrderAddressDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\EntityExtension;
use Shopware\Core\Framework\DataAbstractionLayer\FieldCollection;
use Shopware\Core\Framework\DataAbstractionLayer\Field\OneToManyAssociationField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\RestrictDelete;

class OrderAddressExtension extends EntityExtension
{
    public function extendFields(FieldCollection $collection): void
    {
        $collection->add(
            (new OneToManyAssociationField('orderSender', OrderDeliveryDefinition::class, 'sender_order_address_id', 'id'))->addFlags(new RestrictDelete())
        );
    }

    public function getDefinitionClass(): string
    {
        return OrderAddressDefinition::class;
    }
}

Das ist mein aktueller Stand. Ich weiß, dass ich die Daten noch irgendwie speichern muss.

Hier wird ein Teil dessen beschrieben, was ich gemacht habe: Adding Complex Data to Existing Entities | Shopware Documentation

Allerdings wird dort eine gänzlich neue Tabelle mit neuer Entity und EntityCollection erstellt.

Das will ich ja nicht: Hier ergänze ich die bestehende Tabelle und erweitere die EntityDefinition. Wie ich bestehende Entities und ggf. EntityCollections erweitere, habe ich nicht gefunden.

Ich hoffe jemand hat es bis hierhin geschafft und ist willens, mir dabei zu helfen. Ich bin langsam am Ende meines Lateins.

Hier noch der relevante Teil von services.xml:

        <service id="MyPlugin\Extension\Checkout\Order\Aggregate\OrderDelivery\OrderDeliveryExtension">
            <tag name="shopware.entity.extension"/>
        </service>
        <service id="MyPlugin\Extension\Checkout\Order\Aggregate\OrderAddress\OrderAddressExtension">
            <tag name="shopware.entity.extension"/>
        </service>

Die Collection musst du nicht erweitern, dass ist ja mehr oder weniger nur ein Array um einzelne Entities.

Was noch komplett fehlt ist die Storefront. Das im Checkout unterzubringen ist leider recht kompliziert, da viele Daten in Cookies zwischengespeichert werden und erst bei der Erstellung der Bestellung in die Datenbank geschrieben.

Hast du in den AGB der Dienstleister nachgelesen, ob du das überhaupt rechtlich darfst?

Danke für deine Antwort.

Die Storefront ist der zweite Schritt. Auch hier plane ich, die Checkbox samt Felder für die abweichende Lieferadresse zu kopieren. Mal sehen, wie gut das dann läuft …

Danach muss es in der Dokumenten-Erstellung berücksichtigt werden: Neue Absender-Adresse ausgeben und das Standard-Dokument ohne Branding anstatt des Verkaufskanal-spezifischen Dokuments verwenden.

Meinst du die Versand-Dienstleister? Wir migrieren unsere bestehenden Shops, die seit Jahren neutralen Versand haben. Ist nicht meine Aufgabe, ich gehe aber davon aus, dass die Verantwortlichen besagte AGB geprüft haben.

Wenn niemand eine bessere Lösung kennt, versuche ich es doch mal mit einer eigenen Tabelle samt Entity.