How to convert existing Data Object to variant?

I’m wondering if there’s a way from the admin interface for an end-user to convert an already created item to a variant?

Technically, the only thing that differences a variant from a regular child-item, is a column in the database (table object, column o_type). Via API you can change the type freely $objcet->setType(AbstractObject::OBJECT_TYPE_VARIANT). There is no way using the Pimcore UI to do that though, but you can create an extension that does that for you, as I did in a project:

Sounds good. I’m surprised that’s not just a standard feature, especially since the implementation would be so simple. I’ll have products being added from many channels, and most won’t come with a predetermined parent.

I was able to get that added. Curious, is there a way to have it refresh the object on success so all the children display the proper icon?

this is my JS

pimcore.registerNS("zeroridge.tree.variant_convert_context_menu");

zeroridge.tree.variant_convert_context_menu = Class.create(pimcore.plugin.admin, {
    getClassName: function() {
        return "zeroridge.tree.variant_convert_context_menu";
    },

    initialize: function() {
        pimcore.plugin.broker.registerPlugin(this);
    },

    prepareObjectTreeContextMenu: function (tree, treeClass, menuItem) {
        if (!menuItem.data.hasOwnProperty('className')) {
            return;
        }

        if (menuItem.data.className !== coreshop.class_map.coreshop.product) {
            return;
        }

        tree.add([
            { xtype: 'menuseparator' },
            {
                text: 'Convert childs to Variants',
                iconCls: "pimcore_icon_variant",
                handler: function() {
                    Ext.Ajax.request({
                        url: '/admin/zeroridge/convert-childs-to-variants',
                        params: {
                            id: menuItem.data.id
                        },
                        success: function (response) {
                            treeClass.reloadNode(tree, menuItem);
                        }
                    });
                }
            }
        ]);
    },
});

new zeroridge.tree.variant_convert_context_menu();

this is my controller:

<?php

namespace AppBundle\Controller\Admin;

use AppBundle\Model\ProductInterface;
use AppBundle\Model\ProductPackageTypeInterface;
use CoreShop\Bundle\WarehouseBundle\Notes;
use Pimcore\Model\Asset;
use Pimcore\Model\DataObject\Concrete;
use Pimcore\Model\DataObject\ZeroRidgeProduct;
use Pimcore\Model\DataObject\ZeroRidgeProductPackageType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class AdminController extends \Pimcore\Bundle\AdminBundle\Controller\AdminController
{
    public function convertChildsToVariantsAction(Request $request)
    {
        $id = $request->get('id');
        $object = Concrete::getById($id);

        if (!$object instanceof ProductInterface) {
            throw new NotFoundHttpException();
        }

        $children = $object->getChildren([Concrete::OBJECT_TYPE_OBJECT], true);

        /**
         * @var Concrete $child
         */
        foreach ($children as $child) {
            $child->setType(Concrete::OBJECT_TYPE_VARIANT);
            $child->save();
        }

        return $this->json(['success' => true]);
    }
}

with that, the tree reloads as well

Thank you for going above and beyond. I imagine that will help others out as well.