devkaybadevkayba MemberComments: 7 Received thanks: 0 Member since: January 16

Hallo zusammen,

ich möchte via Plugin im Backend die Möglichkeit bereitstellen, dass man zusätzlich zu Dokumenten, wie z.B. Rechnung, Lieferschein usw. auch ein Angebot erstellen kann. Die enstprechenden Datenbankeinträge, für die Auswahl des Angebot-Dokuments habe ich soweit fertig. Nun wird allerdings beim Versuch so eins zu erzeugen natürlich folgender Fehler ausgegen

{
  "errors": [{
    "status": "400",
    "code": "DOCUMENT__INVALID_GENERATOR_TYPE",
    "title": "Bad Request",
    "detail": "Unable to find a document generator with type \u0022offer\u0022",
    "meta": {
      "parameters": []
    }
  }]
}

Diese Fehlermeldung stammt vermutlich aus der Datei:

platform/src/Core/Checkout/Document/Exception/InvalidDocumentGeneratorTypeException.php

Mein Angebot soll ganz simpel eine komplette Kopie der Rechnung sein (Damit ist Inhalt und Aufbau gemeint). Der einzige Unterschied, anstelle vom Wort Rechnung, soll natürlich das Wort Angebot erscheinen. Der Rest bleibt vom handling gleich.

Die Rechnung wird denke ich mit folgender Datei definiert:

platform/src/Core/Checkout/Document/DocumentGenerator/InvoiceGenerator.php

Diesen Pfad habe ich in meinem Plugin nachgebildet, die InvoiceGenerator.php kopiert, umbenannt und inhaltlich anstelle von Invoice mit dem Begriff "offer" gearbeitet:

PLUGIN_ROOT/src/Core/Checkout/Document/DocumentGenerator/OfferGenerator.php

Nun meine Frage, wie bringe ich Shopware bei, meine OfferGenerator.php zu laden und beim erstellen eines Angebots (offer) keinen Fehler auszuspucken, sondern die Datei zu generieren?

Zusatzfrage: In der InvoiceGenerator.php  wird als Template für die Rechnung '@Framework/documents/invoice.html.twig' angegeben. Ich checke nicht, wo das im gesamten Baum liegt. Sprich, ich finde diese Datei nicht. Denn ich denke mal, ich müsste auch diesen Pfad in meinem Plugin abbilden und entsprechend eine 'offer.html.twig' anlegen.

Ich bin echt am verzweifeln.

Answers

  • mowlwurfmowlwurf MemberComments: 46 Received thanks: 12 edited February 13 Member since: April 2017

    Hi,

    Du brauchst noch den Eintrag in der services.xml mit dem entsprechenden Tag document.generator

    <service id="Deine\Service\ID">
        <argument type="service" id="Shopware\Core\Checkout\Document\Twig\DocumentTemplateRenderer"/>
        <argument>%kernel.root_dir%</argument>
    
        <tag name="document.generator"/>
    </service>

    und in der OfferGenerator Klasse die Funktion supports entsprechend Deines Dokumenten Typs anpassen

    
        public function supports(): string
        {
            return 'offer';
        }

     

    Thanked by 1devkayba
  • devkaybadevkayba MemberComments: 7 Received thanks: 0 edited February 13 Member since: January 16

    Hi,

    Du brauchst noch den Eintrag in der services.xml mit dem entsprechenden Tag document.generator

    <service id="Deine\Service\ID">
        <argument type="service" id="Shopware\Core\Checkout\Document\Twig\DocumentTemplateRenderer"/>
        <argument>%kernel.root_dir%</argument>
    
        <tag name="document.generator"/>
    </service>

     

    Vielen Dank für den Anstoß. Das hat mich einen ganzen Schritt weiter gebracht. Nun gibt es allerdings folgende Fehlermeldung beim Generieren der Datei:

    Return value of Shopware\\Core\\Checkout\\Document\\DocumentConfiguration::getDocumentNumber() must be of the type string or null, int returned

    Nachdem ich meinen Service registriert habe, gab es auch Probleme mit einer Klasse.

    Meine "OfferGenerator.php" ist ja eine 1 zu 1 Kopie der "InvoiceGenerator.php". Inhaltlich habe ich lediglich aus "invoice" "offer" gemacht. Allerdings beginnt die Klasse wie folgt:

    class OfferGenerator implements DocumentGeneratorInterface
    {

    Daraufhin gab es erst mal die Fehlermeldung:

    Attempted to load interface "DocumentGeneratorInterface" from namespace "CustomOrder\Core\Checkout\Document\DocumentGenerator". Did you forget a "use" statement for "Shopware\Core\Checkout\Document\DocumentGenerator\DocumentGeneratorInterface"?

    Deshalb habe ich dann selber händisch die Zeile:

    use Shopware\Core\Checkout\Document\DocumentGenerator\DocumentGeneratorInterface;

    hinterlegt, weil sie vorher nicht da war.

    Und nun bin ich eben da angekommen, das oben beschriebener Fehler ausgespuckt wird.

    Ich spüre, ich bin ganz nah am Ende und kurz vorm Ziel. Bei den etzten wichtigen Schritten tue ich mich aber gerade etwas schwer.

    Aber schon mal Danke für Deine vorherige Hilfe!

    Ich habe jetzt die letzten 3 Wochen alles mögliche an eigenen Pluginfunktionen rumprobiert und viel in der View gearbeitet. Via Migrate ist es mir auch gelungen, eigene Tabellen in der Datenbank anzulegen. Allerdings ist es für mich noch ein kleines Rätsel, wie ich sowohl im Frontend, als auch im Backend an meine eigenen Tabellen und deren Daten rankomme und diese dann auch noch in verschiedenen .html.twig Dateien ausgeben kann. Eigene Routes fürs Frontend und eigene Module mit eigenen Routes fürs Backend habe ich erfolgreich erstellt. Aber wie übergebe ich da jeweils Daten? Bzw. komme erst mal daran?

    Es gibt zwar Beispiele, aber die spielen sich entweder im headerPagelet Subscriber ab, oder Product Subscriber und greifen auf bereits bestehende shopware Daten zurück. Schlicht gesagt, würde ich gerne via "use Doctrine\DBAL\Connection;" eine Verbindung zur Datenbank aufbauen und entsprechende Abfragen machen. 

    Aber wenn ich zum Beispie folgendes mache

    public function onHeaderPageletLoaded(HeaderPageletLoadedEvent $event): void
        {
            $connection = $this->container->get(Connection::class);
            $connection->fetchColumn(
                'SELECT * FROM `product_reservation` a WHERE `CAST(a.end_date AS Datetime)` >= :today',
                ['today' => date('yy-m-d H:i:s', strtotime("+1 hour"))]
            );
            var_dump($connection);
            die;
    }

    dann gibt es direkt ne Fehlermeldung, sobald ich container nutze:

    Notice: Undefined property: ProduktReservieren\Subscriber\Reservieren::$container

     

    Und wie ich die Daten aus einer Beliebigen Klasse (also theoretisch einer komplett eigenen) an .html.twig übergebe , ist für mich noch ein kleines Rätsel.

    Aber DANN hätte ich alles erledigt Grin

    LG
    Manuel

  • mowlwurfmowlwurf MemberComments: 46 Received thanks: 12 Member since: April 2017

    Hallo Manuel,

    zu der Datenbank, jede eigene und auch freme Entity liefert automatisch ein Repository mit über welches Du die Daten auslesen, schreiben oder löschen kannst. Würde ich auch generell ggü. direkten Datenbankabfragen empfehlen.

    Beispiel dafür findest Du hier https://docs.shopware.com/en/shopware-platform-dev-en/how-to/reading-entities-dal

    Da es sich nun im Gegensatz zu Shopware5 um Symfony Controller handelt, kannst Du Services direkt mit $this->get('service.id'), statt $this->container abfragen. Oder noch besser die Services im Constructor Deiner Klasse injecten

    /**
         * OfferController constructor.
         * @param OfferDocumentService $documentService
         * @param ProductLoader $productLoader
         * @param CartService $cartService
         * @param GenericPageLoader $genericPageLoader
         * @param NumberRangeValueGenerator $valueGenerator
         * @param EntityRepositoryInterface $countryRepository
         * @param EntityRepositoryInterface $salutationRepository
         * @param EntityRepositoryInterface $salesChannelRepository
         */
        public function __construct(
            OfferDocumentService $documentService,
            ProductLoader $productLoader,
            CartService $cartService,
            GenericPageLoader $genericPageLoader,
            NumberRangeValueGenerator $valueGenerator,
            EntityRepositoryInterface $countryRepository,
            EntityRepositoryInterface $salutationRepository,
            EntityRepositoryInterface $salesChannelRepository
        )

    use Statements nicht vergessen :) Falls Du mit PhpStorm arbeitest kannst diese auch direkt importieren lassen.

    <service id="dein\Controller" public="true">
                <argument type="service" id="dein\eigener\Service"/>
                <argument type="service" id="Shopware\Storefront\Page\Product\ProductLoader"/>
                <argument type="service" id="Shopware\Core\Checkout\Cart\SalesChannel\CartService"/>
                <argument type="service" id="Shopware\Storefront\Page\GenericPageLoader"/>
                <argument type="service" id="Shopware\Core\System\NumberRange\ValueGenerator\NumberRangeValueGeneratorInterface"/>
                <argument type="service" id="country.repository"/>
                <argument type="service" id="salutation.repository"/>
                <argument type="service" id="sales_channel.repository"/>
    
                <call method="setContainer">
                    <argument type="service" id="service_container"/>
                </call>
            </service>

    Variablen ans Template übergeben tust Du beim Rendern wie folgt in der Controller Action selbst

    return $this->renderStorefront('@Storefront/storefront/page/offer/index.html.twig', [
                'productId'   => $productId,
                'quantity'    => $quantity,
                'cartToken'   => $cartToken,
                'countries'   => $countries,
                'salutations' => $salutations,
                'page'        => $page
            ]);

    Zu Deinem Fehler mit der DocumentNumber, hast Du denn eine eigene NumberRange für das offer Dokument angelegt? Oder nutzt Du eine bestehende?

  • devkaybadevkayba MemberComments: 7 Received thanks: 0 Member since: January 16

    Wow, das ist aber mal ne Menge guter Input. Vielen Dank!

    Das mit dem "Variablen ans Template übergeben" hilft mir mit Sicherheit mega weiter!

    Zu der DocumentNumber: Habe ich nicht. Wo und in welcher Form müsste ich denn diese NumberRange anlegen?

    Ich arbeite mit Visual Studio Code. Muss daher die use statements händisch einfügen.

    Dein gezeigter Service: Ist in dem Fall dann "dein\Controller" einfach mein Pluginname?

    Und das Thema Datenbankabfragen:

    Ich stelle mich echt zu dämlich an. meine Tabelle lautet "product_reservation".

    Müsste meine Abfrage dann so funktionieren?

    $connection = $this->get('product_reservation');

    Und habe ich dann in $connection alle daten der Tabelle als array?

    Denn aktuell bekomme ich, wenn ich Deinem Rat folge und "container" weglasse und nur "get" nehme folgende Fehlermeldung:

    Attempted to call an undefined method named "get" of class "ProduktReservieren\Subscriber\Reservieren".
    Did you mean to call "getSubscribedEvents"?

    Sorry, ich komme mir an dieser stelle echt etwas blöde vor. Und ich bin eigentlich auch gar nicht der Fan davon, anderen die Zeit zu stehlen und nach Hilfe zu fragen. Alles andere habe ich letztendlich auch durch lerning by doing rausgefunden und mich eingearbeitet. Aber an diesen Knackpunkten hänge ich nun länger und komme nicht wirklich mal so richtig weiter. Es sind mehrere Plugins, die ich parallel zum basteln programmiere, um mich eben wirklich ins backend und frontend einzuarbeiten.

    Und an dieser Stelle noch ein großes Danke für Deine vorherige Antwort.

    LG
    Manuel

  • mowlwurfmowlwurf MemberComments: 46 Received thanks: 12 Member since: April 2017

    Hallo Manuel,

    NumberRanges werden in den gleichnamigen Tabellen und deren Entities gepflegt. 

    Dein gezeigter Service: Ist in dem Fall dann "dein\Controller" einfach mein Pluginname?

    "dein\Controller" ist der Namespace + der Klassenname Deines Controllers

    HowTo: https://docs.shopware.com/en/shopware-platform-dev-en/how-to/custom-storefront-controller

    $this->get('product_reservation'); müsste $this->get('product_reservation.repository'); sein, dann bekommst Du das Repository nicht die Connection. Mit dem Repository kannst Du dann die Daten abfragen. Nochmal hier das Beispiel https://docs.shopware.com/en/shopware-platform-dev-en/how-to/reading-entities-dal

    Bzgl. Deinem Fehler im Subscriber:

    Nur in einem Symfony Controller steht der DependencyInjection Container direkt bereit. In einem Subscriber musst Du Services immer über den Constructor und die services.xml Definition einbinden.

    Ich würde Dir empfehlen die kostenlose Videoschulung von Shopware und die HowTos anzuschauen, hier gibt es viele detailierte Beispiele und es wird auch Symfony ein wenig erklärt.

    https://academy.shopware.com/courses/shopware-6-developer-training-english

     

    Thanked by 1devkayba
  • devkaybadevkayba MemberComments: 7 Received thanks: 0 Member since: January 16

    Ok, noch mal danke! Ich hatte mir bisher bereits andere Video Tutorials bzgl. Shopware 6 Programmierung angeschaut und immer wieder mit der offiziellen Dokumentation gearbeitet. Deine weiteren Tipps und Hilfestellungen hier, geben mir den richtigen Anstoß in die richtige Richtung, sodass ich mich weiter reinhängen kann.

     

    Vielen Dank und schon mal ein schönes Wochenende,
    Manuel

  • devkaybadevkayba MemberComments: 7 Received thanks: 0 Member since: January 16

    ... In einem Subscriber musst Du Services immer über den Constructor und die services.xml Definition einbinden. ...

    Also auch nach den Video Tutorials und weiteren Recherchen in der Doku hänge ich an diesem Punkt.

    Wie muss der constructor aussehen? Und an welcher Stelle, damit ich an die Daten komme? Letztendlich möchte ich alle Daten von meiner Tabelle "product_reservation" im Frontend erhalten und dann damit in den twig.html Dateien arbeiten können.

    Mein Subscriber sieht aktuell so aus:

    <?php declare(strict_types=1);
    
    namespace ProduktReservieren\Subscriber;
    
    use Doctrine\DBAL\Connection;
    use Shopware\Core\Content\Product\ProductEvents;
    use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityLoadedEvent;
    use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
    use Symfony\Component\EventDispatcher\EventSubscriberInterface;
    
    use Shopware\Bundle\MediaBundle\MediaService;
    use Shopware\Components\CSRFWhitelistAware;
    use Shopware\Components\Logger;
    use Shopware\Components\Model\ModelManager;
    use Shopware\Models\Customer\Address;
    use Shopware\Models\Customer\Customer;
    
    class Reservieren implements EventSubscriberInterface
    {
        public static function getSubscribedEvents(): array
        {
            return[
                ProductEvents::PRODUCT_LOADED_EVENT => 'onProductsLoaded'
            ];
        }
        
    
        public function onProductsLoaded(EntityLoadedEvent $event)
        {
            echo 'Subscriber works!';
            
        }
    }

     

Sign In or Register to comment.