Product Form - Add Custom Sub-Part

Help for integrating the Laravel package
Forum rules
Always add your Laravel, Aimeos and PHP version as well as your environment (Linux/Mac/Win)
Spam and unrelated posts will be removed immediately!
mohal_04
Advanced
Posts: 108
Joined: 27 Mar 2018, 05:59

Product Form - Add Custom Sub-Part

Post by mohal_04 » 17 Apr 2018, 12:09

Hi,

Please, guide me how to add a new sub-part into Product edit form in Admin Dashboard? See screen-shot:
Sales Tax Sub part
Sales Tax Sub part
salestax_subpart.jpg (85.58 KiB) Viewed 6593 times
Regards!

User avatar
aimeos
Administrator
Posts: 7836
Joined: 01 Jan 1970, 00:00

Re: Product Form - Add Custom Sub-Part

Post by aimeos » 18 Apr 2018, 07:34

Add a new implementation named after your project (not "Standard") in your own extension in this directory:
./admin/jqadm/src/Admin/JQAdm/Product/<subpart name>/<project name>.php

Implement the <project name>.php according to an existing class in the product subdirectories that fits your need best:
https://github.com/aimeos/ai-admin-jqad ... dm/Product

Add a template named ./admin/jqadm/templates/product/item-<subpart name>-<project name>.php in your own extension like one of these:
https://github.com/aimeos/ai-admin-jqad ... es/product

Configure the name of your new class in the ./config/admin.php file of your extension using ./admin/jqadm/product/<subpart name>/name like this one:
https://aimeos.org/docs/Configuration/C ... egory/name

Configure the new subpart name in the ./config/admin.php file of your extension:
https://aimeos.org/docs/Configuration/C ... d/subparts
Professional support and custom implementation are available at Aimeos.com
If you like Aimeos, Image give us a star

mohal_04
Advanced
Posts: 108
Joined: 27 Mar 2018, 05:59

Re: Product Form - Add Custom Sub-Part

Post by mohal_04 » 18 Apr 2018, 10:58

aimeos wrote:Add a new implementation named after your project (not "Standard") in your own extension in this directory:
./admin/jqadm/src/Admin/JQAdm/Product/<subpart name>/<project name>.php

Implement the <project name>.php according to an existing class in the product subdirectories that fits your need best:
https://github.com/aimeos/ai-admin-jqad ... dm/Product

Add a template named ./admin/jqadm/templates/product/item-<subpart name>-<project name>.php in your own extension like one of these:
https://github.com/aimeos/ai-admin-jqad ... es/product

Configure the name of your new class in the ./config/admin.php file of your extension using ./admin/jqadm/product/<subpart name>/name like this one:
https://aimeos.org/docs/Configuration/C ... gory/namee

Configure the new subpart name in the ./config/admin.php file of your extension:
https://aimeos.org/docs/Configuration/C ... d/subparts
Hi,

Thank you very much for the reply! I am again stuck! :'(

Anyhow, I have made changes in code according to your instructions but I am getting following error:

Class "\Aimeos\MShop\Salestax\Manager\Factory" not available

Please, look at my code below and correct me where I am doing wrong...
aimeos wrote: Add a new implementation named after your project (not "Standard") in your own extension in this directory:
./admin/jqadm/src/Admin/JQAdm/Product/<subpart name>/<project name>.php

Implement the <project name>.php according to an existing class in the product subdirectories that fits your need best:
https://github.com/aimeos/ai-admin-jqad ... dm/Product
So, according to your 1st & 2nd instructions, I added a new implementation. Please, check code below...

./ext/myextension/admin/jqadm/src/Admin/JQAdm/Product/Salestax/Mysalestax.php

Code: Select all

namespace Aimeos\Admin\JQAdm\Product\Salestax;

sprintf('salestax'); // for translation


/**
 * Default implementation of product salestax JQAdm client.
 *
 * @package Admin
 * @subpackage JQAdm
 */
class Mysalestax
	extends \Aimeos\Admin\JQAdm\Common\Admin\Factory\Base
	implements \Aimeos\Admin\JQAdm\Common\Admin\Factory\Iface
{
	private $subPartPath = 'admin/jqadm/product/salestax/mysalestax/subparts';
	private $subPartNames = [];


	/**
	 * Copies a resource
	 *
	 * @return string HTML output
	 */
	public function copy()
	{
		$view = $this->addViewData( $this->getView() );

		$view->salestaxData = $this->toArray( $view->item, true );
		$view->salestaxBody = '';

		foreach( $this->getSubClients() as $client ) {
			$view->salestaxBody .= $client->copy();
		}

		return $this->render( $view );
	}


	/**
	 * Creates a new resource
	 *
	 * @return string HTML output
	 */
	public function create()
	{
		$view = $this->addViewData( $this->getView() );
		$siteid = $this->getContext()->getLocale()->getSiteId();
		$data = $view->param( 'salestax', [] );

		foreach( $view->value( $data, 'salestax.id', [] ) as $idx => $value ) {
			$data['salestax.siteid'][$idx] = $siteid;
		}

		$view->salestaxData = $data;
		$view->salestaxBody = '';

		foreach( $this->getSubClients() as $client ) {
			$view->salestaxBody .= $client->create();
		}

		return $this->render( $view );
	}


	/**
	 * Deletes a resource
	 */
	public function delete()
	{
		parent::delete();

		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'salestax' );

		$code = $this->getView()->item->getCode();
		$search = $manager->createSearch()->setSlice( 0, 0x7fffffff );
		$search->setConditions( $search->compare( '==', 'salestax.productcode', $code ) );

		$manager->deleteItems( array_keys( $manager->searchItems( $search ) ) );
	}


	/**
	 * Returns a single resource
	 *
	 * @return string HTML output
	 */
	public function get()
	{
		$view = $this->addViewData( $this->getView() );

		$view->salestaxData = $this->toArray( $view->item );
		$view->salestaxBody = '';

		foreach( $this->getSubClients() as $client ) {
			$view->salestaxBody .= $client->get();
		}

		return $this->render( $view );
	}


	/**
	 * Saves the data
	 */
	public function save()
	{
		$view = $this->getView();
		$context = $this->getContext();

		$manager = \Aimeos\MShop\Factory::createManager( $context, 'salestax' );
		$manager->begin();

		try
		{
			$this->fromArray( $view->item, $view->param( 'salestax', [] ) );
			$view->salestaxBody = '';

			foreach( $this->getSubClients() as $client ) {
				$view->salestaxBody .= $client->save();
			}

			$manager->commit();
			return;
		}
		catch( \Aimeos\MShop\Exception $e )
		{
			$error = array( 'product-item-salestax' => $context->getI18n()->dt( 'mshop', $e->getMessage() ) );
			$view->errors = $view->get( 'errors', [] ) + $error;
			$this->logException( $e );
		}
		catch( \Exception $e )
		{
			$error = array( 'product-item-salestax' => $e->getMessage() . ', ' . $e->getFile() . ':' . $e->getLine() );
			$view->errors = $view->get( 'errors', [] ) + $error;
			$this->logException( $e );
		}

		$manager->rollback();

		throw new \Aimeos\Admin\JQAdm\Exception();
	}


	/**
	 * Returns the sub-client given by its name.
	 *
	 * @param string $type Name of the client type
	 * @param string|null $name Name of the sub-client (Default if null)
	 * @return \Aimeos\Admin\JQAdm\Iface Sub-client object
	 */
	public function getSubClient( $type, $name = null )
	{
		return $this->createSubClient( 'product/salestax/' . $type, $name );
	}


	/**
	 * Adds the required data used in the salestax template
	 *
	 * @param \Aimeos\MW\View\Iface $view View object
	 * @return \Aimeos\MW\View\Iface View object with assigned parameters
	 */
	protected function addViewData( \Aimeos\MW\View\Iface $view )
	{
		$typeManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'salestax/type' );

		$view->salestaxTypes = $typeManager->searchItems( $typeManager->createSearch() );

		return $view;
	}


	/**
	 * Returns the list of sub-client names configured for the client.
	 *
	 * @return array List of JQAdm client names
	 */
	protected function getSubClientNames()
	{
		return $this->getContext()->getConfig()->get( $this->subPartPath, $this->subPartNames );
	}


	/**
	 * Creates new and updates existing items using the data array
	 *
	 * @param \Aimeos\MShop\Product\Item\Iface $item Product item object without referenced domain items
	 * @param string[] $data Data array
	 */
	protected function fromArray( \Aimeos\MShop\Product\Item\Iface $item, array $data )
	{
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'salestax' );

		$search = $manager->createSearch();
		$search->setConditions( $search->compare( '==', 'salestax.productcode', $item->getCode() ) );
		$salestaxItems = $manager->searchitems( $search );

		$list = (array) $this->getValue( $data, 'salestax.id', [] );

		$manager->deleteItems( array_diff( array_keys( $salestaxItems ), $list ) );

		foreach( $list as $idx => $id )
		{
			if( !isset( $salestaxItems[$id] ) ) {
				$salestaxItem = $manager->createItem();
			} else {
				$salestaxItem = $salestaxItems[$id];
			}

			$salestaxItem->setProductCode( $item->getCode() );
			$salestaxItem->setTypeId( $this->getValue( $data, 'salestax.typeid/' . $idx ) );
			$salestaxItem->setSalestaxlevel( $this->getValue( $data, 'salestax.salestaxlevel/' . $idx ) );
			$salestaxItem->setDateBack( $this->getValue( $data, 'salestax.dateback/' . $idx ) );

			$manager->saveItem( $salestaxItem, false );
		}
	}


	/**
	 * Constructs the data array for the view from the given item
	 *
	 * @param \Aimeos\MShop\Product\Item\Iface $item Product item object including referenced domain items
	 * @param boolean $copy True if items should be copied, false if not
	 * @return string[] Multi-dimensional associative list of item data
	 */
	protected function toArray( \Aimeos\MShop\Product\Item\Iface $item, $copy = false )
	{
		$data = [];
		$context = $this->getContext();
		$siteId = $context->getLocale()->getSiteId();
		$manager = \Aimeos\MShop\Factory::createManager( $context, 'salestax' );

		$search = $manager->createSearch();
		$search->setConditions( $search->compare( '==', 'salestax.productcode', $item->getCode() ) );
		$search->setSortations( array( $search->sort( '+', 'salestax.type.code' ) ) );

		foreach( $manager->searchItems( $search ) as $salestaxItem )
		{
			$list = $salestaxItem->toArray( true );

			if( $copy === true )
			{
				$list['salestax.siteid'] = $siteId;
				$list['salestax.id'] = '';
			}

			$list['salestax.dateback'] = str_replace( ' ', 'T', $list['salestax.dateback'] );

			foreach( $list as $key => $value ) {
				$data[$key][] = $value;
			}
		}

		return $data;
	}


	/**
	 * Returns the rendered template including the view data
	 *
	 * @param \Aimeos\MW\View\Iface $view View object with data assigned
	 * @return string HTML output
	 */
	protected function render( \Aimeos\MW\View\Iface $view )
	{
		$tplconf = 'admin/jqadm/product/salestax/template-item';
		$default = 'product/item-salestax-mysalestax.php';

		return $view->render( $view->config( $tplconf, $default ) );
	}
}
aimeos wrote: Add a template named ./admin/jqadm/templates/product/item-<subpart name>-<project name>.php in your own extension like one of these:
https://github.com/aimeos/ai-admin-jqad ... es/product
Following 3rd instructions. Please, check code below!

./ext/myextension/admin/jqadm/templates/product/item-salestax-mysalestax.php

Code: Select all

$enc = $this->encoder();
$salestaxTypes = $this->get( 'salestaxTypes', [] );

$keys = ['salestax.id', 'salestax.siteid', 'salestax.typeid', 'salestax.salestaxlevel', 'salestax.dateback'];


?>
<div id="salestax" class="item-salestax content-block tab-pane fade" role="tabpanel" aria-labelledby="salestax">

	<table class="salestax-list table table-default"
		data-items="<?= $enc->attr( json_encode( $this->get( 'salestaxData', [] ) ) ); ?>"
		data-keys="<?= $enc->attr( json_encode( $keys ) ) ?>"
		data-prefix="salestax."
		data-siteid="<?= $this->site()->siteid() ?>"
		data-numtypes="<?= count( $salestaxTypes ) ?>" >

		<thead>
			<tr>
				<?php if( count( $salestaxTypes ) > 1 ) : ?>
					<th class="salestax-type">
						<span class="help"><?= $enc->html( $this->translate( 'admin', 'Type' ) ); ?></span>
						<div class="form-text text-muted help-text">
							<?= $enc->html( $this->translate( 'admin', 'Warehouse or local store if your articles are available at several locations' ) ); ?>
						</div>
					</th>
				<?php endif; ?>
				<th class="salestax-salestaxlevel">
					<span class="help"><?= $enc->html( $this->translate( 'admin', 'Salestax level' ) ); ?></span>
					<div class="form-text text-muted help-text">
						<?= $enc->html( $this->translate( 'admin', 'Number of articles currently in salestax, leave empty for an unlimited quantity' ) ); ?>
					</div>
				</th>
				<th class="salestax-databack">
					<span class="help"><?= $enc->html( $this->translate( 'admin', 'Back in salestax' ) ); ?></span>
					<div class="form-text text-muted help-text">
						<?= $enc->html( $this->translate( 'admin', 'Shown if the article reached a salestax level of zero' ) ); ?>
					</div>
				</th>
				<th class="actions">
					<div v-if="(items['salestax.id'] || []).length < numtypes" class="btn act-add fa" tabindex="<?= $this->get( 'tabindex' ); ?>"
						title="<?= $enc->attr( $this->translate( 'admin', 'Insert new entry (Ctrl+I)') ); ?>"
						v-on:click="addItem()">
					</div>
				</th>
			</tr>
		</thead>
		<tbody>

			<tr v-for="(id, idx) in items['salestax.id']" v-bind:key="idx" class="salestax-row">
				<?php if( count( $salestaxTypes ) > 1 ) : ?>
					<td class="salestax-type mandatory">
						<select class="form-control custom-select item-typeid" required="required" tabindex="<?= $this->get( 'tabindex' ); ?>"
							name="<?= $enc->attr( $this->formparam( array( 'salestax', 'salestax.typeid', '' ) ) ); ?>"
							v-bind:readonly="checkSite('salestax.siteid', idx)"
							v-model="items['salestax.typeid'][idx]" >

							<option value="" disable>
								<?= $enc->html( $this->translate( 'admin', 'Please select' ) ); ?>
							</option>

							<?php foreach( $salestaxTypes as $typeId => $typeItem ) : ?>
								<option value="<?= $enc->attr( $typeId ); ?>" v-bind:selected="items['salestax.typeid'][idx] == '<?= $enc->attr( $typeId ) ?>'">
									<?= $enc->html( $typeItem->getLabel() ) ?>
								</option>
							<?php endforeach; ?>
						</select>
					</td>
				<?php else : ?>
					<input class="item-typeid" type="hidden"
						name="<?= $enc->attr( $this->formparam( array( 'salestax', 'salestax.typeid', '' ) ) ); ?>"
						value="<?= $enc->attr( key( $salestaxTypes ) ); ?>" />
				<?php endif; ?>
				<td class="salestax-salestaxlevel optional">
					<input class="form-control item-salestaxlevel" type="number" step="1" min="0" tabindex="<?= $this->get( 'tabindex' ); ?>"
						name="<?= $enc->attr( $this->formparam( array( 'salestax', 'salestax.salestaxlevel', '' ) ) ); ?>"
						v-bind:readonly="checkSite('salestax.siteid', idx)"
						v-model="items['salestax.salestaxlevel'][idx]" />
				</td>
				<td class="salestax-databack optional">
					<input class="form-control item-dateback" type="datetime-local" tabindex="<?= $this->get( 'tabindex' ); ?>"
						name="<?= $enc->attr( $this->formparam( array( 'salestax', 'salestax.dateback', '' ) ) ); ?>"
						placeholder="<?= $enc->attr( $this->translate( 'admin', 'YYYY-MM-DD hh:mm:ss (optional)' ) ); ?>"
						v-bind:readonly="checkSite('salestax.siteid', idx)"
						v-model="items['salestax.dateback'][idx]" />
				</td>
				<td class="actions">
					<input class="item-id" type="hidden" v-model="items['salestax.id'][idx]"
						name="<?= $enc->attr( $this->formparam( array( 'salestax', 'salestax.id', '' ) ) ); ?>" />

					<div v-if="!checkSite('salestax.siteid', idx)" class="btn act-delete fa" tabindex="<?= $this->get( 'tabindex' ); ?>"
						title="<?= $enc->attr( $this->translate( 'admin', 'Delete this entry') ); ?>"
						v-on:click.stop="removeItem(idx)">
					</div>
				</td>
			</tr>

		</tbody>
	</table>

	<?= $this->get( 'salestaxBody' ); ?>

</div>
Next, 4th and 5th instructions...
aimeos wrote: Configure the name of your new class in the ./config/admin.php file of your extension using ./admin/jqadm/product/<subpart name>/name like this one:
https://aimeos.org/docs/Configuration/C ... gory/namee

Configure the new subpart name in the ./config/admin.php file of your extension:
https://aimeos.org/docs/Configuration/C ... d/subparts
./ext/myextension/config/admin.php

Code: Select all

return [
	'jqadm' => [
		'product' => array(
			'salestax' => array(
				'name' => 'Mysalestax'
			),
			'standard' => array(
				'subparts' => array(
					'selection',
					'bundle',
					'image',
					'text',
					'price',
					'stock',
					'category',
					'characteristic',
					'option',
					'related',
					'physical',
					'download',
					'subscription',
					'special',
					'salestax'
				)
			)
		)
	],
	'jsonadm' => [
	],
];
Please, go through all of the code above and advice me what should I do to fix the error. I am waiting for your reply.

Thanks!

User avatar
aimeos
Administrator
Posts: 7836
Joined: 01 Jan 1970, 00:00

Re: Product Form - Add Custom Sub-Part

Post by aimeos » 19 Apr 2018, 07:39

Where is the sales tax data stored? You are trying to use a new manager that doesn't exist yet.
Professional support and custom implementation are available at Aimeos.com
If you like Aimeos, Image give us a star

mohal_04
Advanced
Posts: 108
Joined: 27 Mar 2018, 05:59

Re: Product Form - Add Custom Sub-Part

Post by mohal_04 » 19 Apr 2018, 09:11

aimeos wrote:Where is the sales tax data stored? You are trying to use a new manager that doesn't exist yet.
Hi,

Thanks for reply! Sales Tax data is not stored anywhere because there was no form to insert data. Plus, I have created a Schema file (see below) but this file doesn't create a new table when I run "php artisan aimeos:setup."

./ext/myextension/lib/custom/setup/default/schema/salestax.php

Code: Select all

return array(
	'table' => array(
		'mshop_salestax_type' => function ( \Doctrine\DBAL\Schema\Schema $schema ) {

			$table = $schema->createTable( 'mshop_salestax_type' );

			$table->addColumn( 'id', 'integer', array( 'autoincrement' => true ) );
			$table->addColumn( 'siteid', 'integer', [] );
			$table->addColumn( 'domain', 'string', array( 'length' => 32 ) );
			$table->addColumn( 'code', 'string', array( 'length' => 32 ) );
			$table->addColumn( 'label', 'string', array( 'length' => 255 ) );
			$table->addColumn( 'status', 'smallint', [] );
			$table->addColumn( 'mtime', 'datetime', [] );
			$table->addColumn( 'ctime', 'datetime', [] );
			$table->addColumn( 'editor', 'string', array( 'length' => 255 ) );

			$table->setPrimaryKey( array( 'id' ), 'pk_msstoty_id' );
			$table->addUniqueIndex( array( 'siteid', 'domain', 'code' ), 'unq_msstoty_sid_dom_code' );
			$table->addIndex( array( 'siteid', 'status' ), 'idx_msstoty_sid_status' );
			$table->addIndex( array( 'siteid', 'label' ), 'idx_msstoty_sid_label' );
			$table->addIndex( array( 'siteid', 'code' ), 'idx_msstoty_sid_code' );

			return $schema;
		},

		'mshop_salestax' => function ( \Doctrine\DBAL\Schema\Schema $schema ) {

			$table = $schema->createTable( 'mshop_salestax' );

			$table->addColumn( 'id', 'integer', array( 'autoincrement' => true ) );
			$table->addColumn( 'siteid', 'integer', [] );
			$table->addColumn( 'typeid', 'integer', [] );
			$table->addColumn( 'productcode', 'string', array( 'length' => 32 ) );
			$table->addColumn( 'salestaxlevel', 'integer', array( 'notnull' => false ) );
			$table->addColumn( 'backdate', 'datetime', array( 'notnull' => false ) );
			$table->addColumn( 'mtime', 'datetime', [] );
			$table->addColumn( 'ctime', 'datetime', [] );
			$table->addColumn( 'editor', 'string', array( 'length' => 255 ) );

			$table->setPrimaryKey( array( 'id' ), 'pk_mssto_id' );
			$table->addUniqueIndex( array( 'siteid', 'productcode', 'typeid' ), 'unq_mssto_sid_pcode_tid' );
			$table->addIndex( array( 'siteid', 'salestaxlevel' ), 'idx_mssto_sid_salestaxlevel' );
			$table->addIndex( array( 'siteid', 'backdate' ), 'idx_mssto_sid_backdate' );
			$table->addIndex( array( 'typeid' ), 'fk_mssto_typeid' );

			$table->addForeignKeyConstraint( 'mshop_salestax_type', array( 'typeid' ), array( 'id' ),
				array( 'onUpdate' => 'CASCADE', 'onDelete' => 'CASCADE' ), 'fk_mssto_typeid' );

			return $schema;
		},
	),
);
Please, advise!

Thanks!

mohal_04
Advanced
Posts: 108
Joined: 27 Mar 2018, 05:59

Re: Product Form - Add Custom Sub-Part

Post by mohal_04 » 19 Apr 2018, 13:15

aimeos wrote:Where is the sales tax data stored? You are trying to use a new manager that doesn't exist yet.
Hi,

How I can create this new manager? Please, explain in details because there is no resource available on internet.

Thanks!

User avatar
aimeos
Administrator
Posts: 7836
Joined: 01 Jan 1970, 00:00

Re: Product Form - Add Custom Sub-Part

Post by aimeos » 20 Apr 2018, 08:26

For creating tables in new domains, you should read this:
https://aimeos.org/docs/Developers/Libr ... ew_domains

To create new managers and items for a new domains, you can copy/paste code from an existing domain. The stock domain fits best in your case:
https://github.com/aimeos/aimeos-core/t ... Shop/Stock
Professional support and custom implementation are available at Aimeos.com
If you like Aimeos, Image give us a star

mohal_04
Advanced
Posts: 108
Joined: 27 Mar 2018, 05:59

Re: Product Form - Add Custom Sub-Part

Post by mohal_04 » 20 Apr 2018, 11:09

aimeos wrote:For creating tables in new domains, you should read this:
https://aimeos.org/docs/Developers/Libr ... ew_domains

To create new managers and items for a new domains, you can copy/paste code from an existing domain. The stock domain fits best in your case:
https://github.com/aimeos/aimeos-core/t ... Shop/Stock
Hi,

Thanks for the reply! I shall implement this and will let you know if I still struggle.

Thanks!

sixbynine
Posts: 93
Joined: 10 Jan 2018, 11:22

Re: Product Form - Add Custom Sub-Part

Post by sixbynine » 03 May 2018, 18:40

Hello,

Can you tell me if the location for the new manager has to be done like this:

ext/<MYEXTENSION>/lib/mshoplib/src/MShop/<MYMANAGER>

Oops, sorry I found the answer there:

https://aimeos.org/docs/Developers/Libr ... gers_items

Thank you a lot !

sbn

mohal_04
Advanced
Posts: 108
Joined: 27 Mar 2018, 05:59

Re: Product Form - Add Custom Sub-Part

Post by mohal_04 » 04 May 2018, 15:24

sixbynine wrote:Hello,

Can you tell me if the location for the new manager has to be done like this:

ext/<MYEXTENSION>/lib/mshoplib/src/MShop/<MYMANAGER>

Oops, sorry I found the answer there:

https://aimeos.org/docs/Developers/Libr ... gers_items

Thank you a lot !

sbn
Hi,

So, you solved your problem? Sorry, I was busy on other stuff and couldn't reply you earlier.

Thanks!

Post Reply