Backend Artikel um Tab mit Tabelle erweitern

Guten Tag liebe Shopware-Gemeinde,

ich benötige bitte eure Hilfe zu einem Thema, welches mir aktuell ziemliches Kopfzerbrechen bereitet.

Ich arbeite aktuell an einem Kundenprojekt, welches recht vielseitige Erweiterungen benötigt.
Zum größten Teil sind diese bereits durch uns umgesetzt worden, zum Teil durch den Einsatz von Plugins, zum Teil durch Eigenprogrammierung oder Erweiterung von Plugins.

Jetzt geht es jedoch um die Erweiterung der Artikelpflege im Backend - und ich habe einfach ein Brett vor dem Kopf.

Folgende Anforderung: Wir haben ein Plugin am Laufen, welches im Frontend die Suche nach Artikeln per Multi-Dropdown Auswahl oder KBA-Nummern (Fahrzeugteile) ermöglicht. Die Zuweisungen geschehen in einem Freitext-Feld am Artikel, wo ich KType-Nummern per Komma getrennt eintragen kann. Nun möchte der Kunde jedoch im Backend am Artikel die passenden Fahrzeuge gerne auch aus einer Liste auswählen können.

Ich muss also im Artikelbereich einen weiteren Reiter einpflegen, welcher die in der Datenbank hinterlegte Liste aller Fahrzeuge als Tabelle anzeigt. Die einzelnen Spalten (Hersteller, Modell, …) müssen hierbei filterbar sein. Nun möchte der Kunde per Checkbox die einzelnen Fahrzeuge auswählen können und dann speichern.

Ich stelle mir die Optik ähnlich der Artikelübersicht vor. Technisch gesehen ist mir die Funktionsweise auch klar - ich lese die gesamte Fahrzeugliste aus der Datenbank, lese die bestehende Freitextzeile mit den KTypes aus, setze die bereits eingetragenen Nummern in der Tabelle als checked. Soweit so gut. Dann können weitere Fahrzeuge angehakt oder abgehakt werden, beim Klick auf Speichern werden die KType Nummern aller angehakten Fahrzeuge genommen und in das Freitextfeld geschrieben.

Es hapert bei mir an der Umsetzung der Tabellenansicht im eigenen Backendreiter mit Bereitstellung der Daten aus einer Datenbanktabelle sowie der Anforderung, dass auch beim Seitenwechsel oder Filtern (20 Fahrzeuge pro Seite) die gesetzten Haken erhalten bleiben.

Falls jemand schon einmal etwas ähnliches umsetzen musste oder zumindest aus dem Verständnis heraus eine Idee hat oder mich in die richtige Richtung schubsen kann, wäre ich sehr dankbar.

Mir sind zwar sowohl JavaScript, ExtJs als auch Smarty bekannt, da wir bisher jedoch größtenteils Frontend-Erweiterungen programmiert haben, fehlt mir das Verständnis der durch Shopware bereitgestellten Module.

Ich wünsche allen einen schönen Tag und hoffe auf ein paar gute Ideen/Ansätze!

Ich muss hier noch einmal pushen…

Ich bin inzwischen ein ganzes Stück weitergekommen, habe jetzt aber ein Problem mit dem Ziehen der Daten aus der Datenbank.
Ich habe es geschafft, die Artikeldetails im Backend um einen eigenen Reiter zu erweitern und dort ein Grid anzulegen mit den Spalten.

Nun möchte ich die Daten laden - ich glaube aber, hier habe ich das Zusammenspiel von ExtJS und Shopware noch nicht gänzlich verstanden und deshalb gibt es Fehler. Der aktuelle Fehler im Backend lautet einfach:  Shopware.apps.Article - Article?file=app - Unbekannter Typ

Ich habe mir kein eigenes Model angelegt, sondern möchte gerne auf das bestehende Model des Plugins zugreifen, welches ich erweitern möchte. Ansonsten würde ich die Daten in der Datenbank ja doppelt vorhalten.

Ich versuche, die Daten mithilfe des Models des Fremd-Plugins in das Grid zu laden, dort eine Checkbox für jede Zeile einzubauen und sobald eine Checkbox angehakt oder abgehakt wird, soll der Wert in einem Attributfeld am Produkt verändert werden.

Hier mein Code, falls jemand sich die Mühe machen mag, mal drüber zu schauen:

/custom/plugins/HzweiaKbaAddon/Resources/views/backend/hzweia_kba_addon/controller/hzweia_kba_addon.js

/**
 *
 */
//{namespace name=backend/HzweiaKbaAddon/view/HzweiaKbaAddon}
//{block name="backend/article/controller/HzweiaKbaAddon"}
Ext.define('Shopware.apps.HzweiaKbaAddon.controller.HzweiaKbaAddon', {

    /**
     * Defines that this component is a extJs controller extension
     * @string
     */
    extend:'Ext.app.Controller',

    /**
     * all references to get the elements by the applicable selector
     */
    refs:[
        { ref:'genderListGrid', selector:'hzweia_kba_addon-detail-article_kba_list' },
    ],
    public:offerId=0,

    /**
     * @return void
     */
    init:function () {
        var me = this;
        me.control({
            'hzweia_kba_addon-detail-article_kba_list': {

            },
        });
        me.callParent(arguments);
    },

});

/custom/plugins/HzweiaKbaAddon/Resources/views/backend/hzweia_kba_addon/view/article/detail/window.js

//{block name="backend/article/view/detail/window"}
//{$smarty.block.parent}
Ext.define('Shopware.apps.HzweiaKbaAddon.view.article.detail.Window', {
     /**
     * Override the article detail window
     * @string
     */
   override: 'Shopware.apps.Article.view.detail.Window',

  createMainTabPanel: function () {
        var me = this;
        me.callParent(arguments);
        me.registerAdditionalTab({
            title: 'HZWEIA',
            insertIndex: 99,
            tabConfig: {
                bodyPadding: 10
            },
            contentFn: this.createHzweiaKbaTab,
            articleChangeFn: this.refreshHzweiaKbaTab,
            scope: this,
        }, 'Ext.form.Panel'
        );
      return me.mainTab;
  },
  createHzweiaKbaTab: function (article, stores, eOpts) {
    var me = this;
    
    me.hzweiaKbaTab = Ext.create('widget.hzweia_kba_addon-detail-article_kba_list', {SoftwKbaManagerStore: me.SoftwKbaManagerStore});
    eOpts.tab.add(me.hzweiaKbaTab);
    eOpts.tab.setDisabled(false);
    me.refreshHzweiaKbaTab();
  },
  refreshHzweiaKbaTab: function() {
    var me = this;
    
    if (me.article.get('mainDetailId')) {
      Ext.Ajax.request({
        url: '{url controller=AttributeData action=loadData}',
        params: {
          _foreignKey: me.article.get('mainDetailId'),
          _table: 's_articles_attributes'
        },
        success: function (responseData, request) {
          var response = Ext.JSON.decode(responseData.responseText);
          //me.setHzweiaKbaFormValues(response.data);
        }
      });
    }
  },
  setHzweiaKbaFormValues: function (data) {
    var me = this;
    
    
  }
});
//{/block}

 

/custom/plugins/HzweiaKbaAddon/Resources/views/backend/hzweia_kba_addon/view/article/detail/hzweia_kba_tab.js

//{block name="Shopware.apps.HzweiaKbaAddon.view.article.detail.HzweiaKbaTab"}

Ext.define('Shopware.apps.HzweiaKbaAddon.view.article.detail.HzweiaKbaTab', {
    extend: 'Ext.grid.Panel',
    alias: 'widget.hzweia_kba_addon-detail-article_kba_list',
    cls: 'viison-common--grid has--vertical-lines has--no-borders',
    padding: 10,
    title: 'HZWEIA',

    initComponent: function() {
        var me = this;
        me.emptyText = "Fahrzeugliste nicht vorhanden";
        me.columns = me.createColumns();
        me.bbar = me.getPagingbar();
        me.store = me.SoftwKbaManager;
        me.selModel = me.getGridSelModel();

        me.addEvents(
            /**
             * Fired when the user edited a product in the grid
             */
            'saveProduct',

            /**
             * A search was triggered
             */
            'search'
        );

        me.rowEditing = Ext.create('Ext.grid.plugin.RowEditing', {
            clicksToEdit: 2,
            autoCancel: true,
            listeners: {
                scope: me,
                edit: function (editor, context) {
                    me.fireEvent('saveProduct', editor, context)
                }
            }
        });
        me.plugins = me.rowEditing;

        me.listeners = {
            'afterrender': me.onAfterRender
        };

        this.callParent(arguments);
    },
  
     /**
     * @return Ext.grid.column.Column[]
     */
    createColumns: function () {
        var me = this;
        return [{
            dataIndex: 'selected',
            header: 'Ausgewählt',
            flex: 1,
            width: 60,
            renderer: me.booleanColumnRenderer,
            editor: {
                xtype: 'checkbox',
                inputValue: 1,
                uncheckedValue: 0
            }
        }, {
            dataIndex: 'ktype',
            header: "KType",
            flex: 1,
        }, {
            dataIndex: 'kba',
            header: 'KBA-Nummer',
            flex: 1,
        }, {
            dataIndex: 'hersteller',
            header: 'Marke',
            flex: 1,
        }, {
            dataIndex: 'modell',
            header: 'Modell',
            flex: 1,
        }, {
            dataIndex: 'typ',
            header: 'Typ',
            flex: 1,
        }, {
            dataIndex: 'plattform',
            header: 'Plattform',
            flex: 1,
        }, {
            dataIndex: 'baujahr',
            header: 'Baujahr',
            flex: 1,
        }, {
            dataIndex: 'motor',
            header: 'Motor',
            flex: 1,
        }];
    },

    onAfterRender: function() {
        var me = this;
        Ext.each(me.columns, function(col) {
            if (col.dataIndex === 'Detail_active') {
                me.detailActiveColumn = col;
                window.setTimeout(function() { col.setVisible(false); }, 0);
            }
        });
    },
  
    getGridSelModel: function () {
        var me = this;

        return Ext.create('Ext.selection.CheckboxModel', {
            listeners: {
                // Unlocks the delete button if the user has checked at least one checkbox
                selectionchange: function (sm, selections) {
                    me.deleteButton.setDisabled(selections.length === 0);
                    me.splitViewModeBtn.setDisabled(selections.length === 0);
                    me.fireEvent('productchange', selections);
                }
            }
        });
    },
    
    /**
     * Creates rowEditor Plugin
     *
     * @return [Ext.grid.plugin.RowEditing]
     */
    getRowEditorPlugin: function () {
        return Ext.create('Ext.grid.plugin.RowEditing', {
            clicksToEdit: 2,
            errorSummary: false,
            pluginId: 'rowEditing'
        });
    },

    /**
     * Column renderer for boolean columns in order to
     * @param value
     */
    booleanColumnRenderer: function (value) {
        var checked = 'sprite-ui-check-box-uncheck';
        if (value == true) {
            checked = 'sprite-ui-check-box';
        }
        return '';
    },
  
    /**
     * Creates pagingbar
     *
     * @return Ext.toolbar.Paging
     */
    getPagingbar: function () {
        var me = this,
                productSnippet = 'Einträge';

        var pageSize = Ext.create('Ext.form.field.ComboBox', {
            labelWidth: 120,
            cls: Ext.baseCSSPrefix + 'page-size',
            queryMode: 'local',
            width: 180,
            editable: false,
            listeners: {
                scope: me,
                select: me.onPageSizeChange
            },
            store: Ext.create('Ext.data.Store', {
                fields: ['value', 'name'],
                data: [
                    { value: '25', name: '25 ' + productSnippet },
                    { value: '50', name: '50 ' + productSnippet },
                    { value: '75', name: '75 ' + productSnippet },
                    { value: '100', name: '100 ' + productSnippet },
                    { value: '125', name: '125 ' + productSnippet },
                    { value: '150', name: '150 ' + productSnippet }
                ]
            }),
            displayField: 'name',
            valueField: 'value'
        });

        var pagingBar = Ext.create('Ext.toolbar.Paging', {
            dock: 'bottom',
            displayInfo: true
        });

        pagingBar.insert(pagingBar.items.length, [
            { xtype: 'tbspacer', width: 6 },
            pageSize
        ]);

        return pagingBar;
    },

    /**
     * Event listener method which fires when the user selects
     * a entry in the "number of orders"-combo box.
     *
     * @event select
     * @param { object } combo - Ext.form.field.ComboBox
     * @param { array } records - Array of selected entries
     * @return void
     */
    onPageSizeChange: function (combo, records) {
        var record = records[0],
                me = this;

        me.store.pageSize = record.get('value');
        if (!me.store.getProxy().extraParams.ast) {
            return;
        }

        me.store.loadPage(1);
    },
});

//{/block}

/custom/plugins/HzweiaKbaAddon/Resources/views/backend/hzweia_kba_addon/app.js

//{block name="backend/article/application"}
// {$smarty.block.parent}
// {include file="backend/hzweia_kba_addon/controller/hzweia_kba_addon.js"}
// {include file="backend/hzweia_kba_addon/view/article/detail/hzweia_kba_tab.js"}
//{/block}

Und mein bis jetzt noch sehr nichtssagender Backend-Controller:

Request()->getParam('limit', 30);
        $offset = $this->Request()->getParam('start', 0);
        $sort = $this->Request()->getParam('sort', null);

        $builder = Shopware()->Models()->createQueryBuilder();
        $builder->select('b')
            ->from('SoftwKbaManager\Models\Cars', 'b');

        if (!empty($sort)) {
            $sort[0]['property'] = 'b.' . $sort[0]['property'];
            $builder->addOrderBy($sort);
        }

        $builder->setFirstResult($offset)
            ->setMaxResults($limit);
        $query = $builder->getQuery();

        $query->setHydrationMode(\Doctrine\ORM\AbstractQuery::HYDRATE_ARRAY);
        $paginator = Shopware()->Container()->get('Models')->createPaginator($query);
        $total = $paginator->count();

        $data = $query->getArrayResult();

        // sends the out put to the view
        $this->View()->assign(array(
            'success' => true,
            'data' => $data,
            'total' => $total
        ));
    }


    /**
     * @throws Exception
     */
    public function updateListAction()
    {
        try {
            Shopware()->Models()->flush();
        } catch (\Doctrine\ORM\OptimisticLockException $e) {
            $this->View()->assign(
                array(
                    'success' => false,
                    'message' => "Fehler " . print_r($e, true)
                )
            );
        }

        $this->View()->assign(array(
            'success' => true
        ));
    }
}

Mach doch am besten ein Job-Posting.