variant options wip
CI / testsuite (mysql, 8.1, ) (push) Failing after 0s
Details
CI / testsuite (mysql, 8.4, ) (push) Failing after 0s
Details
CI / testsuite (pgsql, 8.1, ) (push) Failing after 0s
Details
CI / testsuite (pgsql, 8.4, ) (push) Failing after 0s
Details
CI / testsuite (sqlite, 8.1, ) (push) Failing after 0s
Details
CI / testsuite (sqlite, 8.1, prefer-lowest) (push) Failing after 0s
Details
CI / testsuite (sqlite, 8.4, ) (push) Failing after 0s
Details
CI / Coding Standard & Static Analysis (push) Failing after 0s
Details
CI / testsuite (mysql, 8.1, ) (push) Failing after 0s
Details
CI / testsuite (mysql, 8.4, ) (push) Failing after 0s
Details
CI / testsuite (pgsql, 8.1, ) (push) Failing after 0s
Details
CI / testsuite (pgsql, 8.4, ) (push) Failing after 0s
Details
CI / testsuite (sqlite, 8.1, ) (push) Failing after 0s
Details
CI / testsuite (sqlite, 8.1, prefer-lowest) (push) Failing after 0s
Details
CI / testsuite (sqlite, 8.4, ) (push) Failing after 0s
Details
CI / Coding Standard & Static Analysis (push) Failing after 0s
Details
This commit is contained in:
parent
c9d34f7115
commit
5adc791c20
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Migrations\BaseMigration;
|
||||
|
||||
class CreateProductCategoryVariantOptions extends BaseMigration
|
||||
{
|
||||
/**
|
||||
* Change Method.
|
||||
*
|
||||
* More information on this method is available here:
|
||||
* https://book.cakephp.org/migrations/4/en/migrations.html#the-change-method
|
||||
* @return void
|
||||
*/
|
||||
public function change(): void
|
||||
{
|
||||
$table = $this->table('product_category_variant_options', ['id' => false, 'primary_key' => ['id']]);
|
||||
$table->addColumn('id', 'uuid', [
|
||||
'default' => null,
|
||||
'null' => false,
|
||||
]);
|
||||
$table->addColumn('product_category_variant_id', 'uuid', [
|
||||
'default' => null,
|
||||
'null' => false,
|
||||
]);
|
||||
$table->addColumn('variant_value', 'string', [
|
||||
'default' => null,
|
||||
'limit' => 255,
|
||||
'null' => false,
|
||||
]);
|
||||
$table->addColumn('variant_label', 'string', [
|
||||
'default' => null,
|
||||
'limit' => 255,
|
||||
'null' => true,
|
||||
]);
|
||||
$table->addColumn('created', 'datetime', [
|
||||
'default' => null,
|
||||
'null' => false,
|
||||
]);
|
||||
$table->addColumn('modified', 'datetime', [
|
||||
'default' => null,
|
||||
'null' => false,
|
||||
]);
|
||||
$table->addColumn('deleted', 'datetime', [
|
||||
'default' => null,
|
||||
'null' => true,
|
||||
]);
|
||||
$table->addColumn('enabled', 'boolean', [
|
||||
'default' => true,
|
||||
'null' => false,
|
||||
]);
|
||||
|
||||
$table->addForeignKey('product_category_variant_id', 'product_category_variants');
|
||||
|
||||
$table->create();
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@ use Cake\ORM\Entity;
|
|||
*
|
||||
* @property ProductCategory|EntityInterface $product_category
|
||||
* @property Product|EntityInterface $product
|
||||
* @property ProductCategoryVariantOption[]|EntityInterface[] $product_category_variant_options
|
||||
*/
|
||||
class ProductCategoryVariant extends Entity
|
||||
{
|
||||
|
@ -34,8 +35,10 @@ class ProductCategoryVariant extends Entity
|
|||
'product_category_id' => true,
|
||||
'product_id' => true,
|
||||
'enabled' => true,
|
||||
|
||||
// entities
|
||||
'product_category' => false,
|
||||
'product' => false,
|
||||
'product_category_variant_options' => true,
|
||||
];
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CakeProducts\Model\Entity;
|
||||
|
||||
use Cake\ORM\Entity;
|
||||
|
||||
/**
|
||||
* ProductCategoryVariantOption Entity
|
||||
*
|
||||
* @property string $id
|
||||
* @property string $product_category_variant_id
|
||||
* @property string $variant_value
|
||||
* @property \Cake\I18n\DateTime $created
|
||||
* @property \Cake\I18n\DateTime $modified
|
||||
* @property \Cake\I18n\DateTime|null $deleted
|
||||
*
|
||||
* @property \App\Model\Entity\ProductCategoryVariant $product_category_variant
|
||||
*/
|
||||
class ProductCategoryVariantOption extends Entity
|
||||
{
|
||||
/**
|
||||
* Fields that can be mass assigned using newEntity() or patchEntity().
|
||||
*
|
||||
* Note that when '*' is set to true, this allows all unspecified fields to
|
||||
* be mass assigned. For security purposes, it is advised to set '*' to false
|
||||
* (or remove it), and explicitly make individual fields accessible as needed.
|
||||
*
|
||||
* @var array<string, bool>
|
||||
*/
|
||||
protected array $_accessible = [
|
||||
'product_category_variant_id' => true,
|
||||
'variant_value' => true,
|
||||
'created' => true,
|
||||
'modified' => true,
|
||||
'deleted' => true,
|
||||
// entities
|
||||
'product_category_variant' => false,
|
||||
];
|
||||
}
|
|
@ -91,6 +91,14 @@ class ProductCategoriesTable extends Table
|
|||
'dependent' => true,
|
||||
'cascadeCallbacks' => true,
|
||||
]);
|
||||
$this->hasMany('ProductCategoryVariants', [
|
||||
'foreignKey' => 'product_category_id',
|
||||
'bindingKey' => 'internal_id',
|
||||
'className' => 'CakeProducts.ProductCategoryVariants',
|
||||
'dependent' => true,
|
||||
'cascadeCallbacks' => true,
|
||||
]);
|
||||
|
||||
$this->behaviors()->Tree->setConfig('scope', ['product_catalog_id' => $this->treeCatalogId]);
|
||||
$this->addBehavior('Muffin/Trash.Trash');
|
||||
}
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CakeProducts\Model\Table;
|
||||
|
||||
use CakeProducts\Model\Entity\ProductCategoryVariantOption;
|
||||
use CakeProducts\Model\Table\ProductCategoryVariantsTable;
|
||||
use Cake\Datasource\EntityInterface;
|
||||
use Cake\Datasource\ResultSetInterface;
|
||||
use Cake\ORM\Association\BelongsTo;
|
||||
use Cake\ORM\Behavior\TimestampBehavior;
|
||||
use Cake\ORM\Query\SelectQuery;
|
||||
use Cake\ORM\RulesChecker;
|
||||
use Cake\ORM\Table;
|
||||
use Cake\Validation\Validator;
|
||||
use Closure;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
|
||||
/**
|
||||
* ProductCategoryVariantOptions Model
|
||||
*
|
||||
* @property ProductCategoryVariantsTable&BelongsTo $ProductCategoryVariants
|
||||
*
|
||||
* @method ProductCategoryVariantOption newEmptyEntity()
|
||||
* @method ProductCategoryVariantOption newEntity(array $data, array $options = [])
|
||||
* @method array<ProductCategoryVariantOption> newEntities(array $data, array $options = [])
|
||||
* @method ProductCategoryVariantOption get(mixed $primaryKey, array|string $finder = 'all', CacheInterface|string|null $cache = null, Closure|string|null $cacheKey = null, mixed ...$args)
|
||||
* @method ProductCategoryVariantOption findOrCreate($search, ?callable $callback = null, array $options = [])
|
||||
* @method ProductCategoryVariantOption patchEntity(EntityInterface $entity, array $data, array $options = [])
|
||||
* @method array<ProductCategoryVariantOption> patchEntities(iterable $entities, array $data, array $options = [])
|
||||
* @method ProductCategoryVariantOption|false save(EntityInterface $entity, array $options = [])
|
||||
* @method ProductCategoryVariantOption saveOrFail(EntityInterface $entity, array $options = [])
|
||||
* @method iterable<ProductCategoryVariantOption>|ResultSetInterface<ProductCategoryVariantOption>|false saveMany(iterable $entities, array $options = [])
|
||||
* @method iterable<ProductCategoryVariantOption>|ResultSetInterface<ProductCategoryVariantOption> saveManyOrFail(iterable $entities, array $options = [])
|
||||
* @method iterable<ProductCategoryVariantOption>|ResultSetInterface<ProductCategoryVariantOption>|false deleteMany(iterable $entities, array $options = [])
|
||||
* @method iterable<ProductCategoryVariantOption>|ResultSetInterface<ProductCategoryVariantOption> deleteManyOrFail(iterable $entities, array $options = [])
|
||||
*
|
||||
* @mixin TimestampBehavior
|
||||
*/
|
||||
class ProductCategoryVariantOptionsTable extends Table
|
||||
{
|
||||
/**
|
||||
* Initialize method
|
||||
*
|
||||
* @param array<string, mixed> $config The configuration for the Table.
|
||||
* @return void
|
||||
*/
|
||||
public function initialize(array $config): void
|
||||
{
|
||||
parent::initialize($config);
|
||||
|
||||
$this->setTable('product_category_variant_options');
|
||||
$this->setDisplayField('variant_value');
|
||||
$this->setPrimaryKey('id');
|
||||
|
||||
$this->addBehavior('Timestamp');
|
||||
|
||||
$this->belongsTo('ProductCategoryVariants', [
|
||||
'foreignKey' => 'product_category_variant_id',
|
||||
'joinType' => 'INNER',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default validation rules.
|
||||
*
|
||||
* @param Validator $validator Validator instance.
|
||||
* @return Validator
|
||||
*/
|
||||
public function validationDefault(Validator $validator): Validator
|
||||
{
|
||||
$validator
|
||||
->uuid('product_category_variant_id')
|
||||
->notEmptyString('product_category_variant_id');
|
||||
|
||||
$validator
|
||||
->scalar('variant_value')
|
||||
->maxLength('variant_value', 255)
|
||||
->requirePresence('variant_value', 'create')
|
||||
->notEmptyString('variant_value');
|
||||
|
||||
$validator
|
||||
->scalar('variant_label')
|
||||
->maxLength('variant_label', 255)
|
||||
->allowEmptyString('variant_label');
|
||||
|
||||
$validator
|
||||
->dateTime('deleted')
|
||||
->allowEmptyDateTime('deleted');
|
||||
|
||||
return $validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a rules checker object that will be used for validating
|
||||
* application integrity.
|
||||
*
|
||||
* @param RulesChecker $rules The rules object to be modified.
|
||||
* @return RulesChecker
|
||||
*/
|
||||
public function buildRules(RulesChecker $rules): RulesChecker
|
||||
{
|
||||
$rules->add($rules->existsIn(['product_category_variant_id'], 'ProductCategoryVariants'), ['errorField' => 'product_category_variant_id']);
|
||||
|
||||
return $rules;
|
||||
}
|
||||
}
|
|
@ -53,6 +53,11 @@ class ProductCategoryVariantsTable extends Table
|
|||
'foreignKey' => 'product_id',
|
||||
'className' => 'CakeProducts.Products',
|
||||
]);
|
||||
|
||||
$this->hasMany('ProductCategoryVariantOptions', [
|
||||
'foreignKey' => 'product_category_variant_id',
|
||||
'className' => 'CakeProducts.ProductCategoryVariantOptions',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -100,4 +105,34 @@ class ProductCategoryVariantsTable extends Table
|
|||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SelectQuery $query
|
||||
* @param string $internalCategoryId
|
||||
*
|
||||
* @return array|\Cake\ORM\Query|SelectQuery
|
||||
*/
|
||||
public function findAllCategoryVariantsForCategoryId(SelectQuery $query, string $internalCategoryId)
|
||||
{
|
||||
$category = $this->ProductCategories->find()->where(['internal_id' => $internalCategoryId])->firstOrFail();
|
||||
|
||||
$this->ProductCategories->behaviors()->get('Tree')->setConfig([
|
||||
'scope' => [
|
||||
'product_catalog_id' => $category->product_catalog_id ?? 1,
|
||||
],
|
||||
]);
|
||||
|
||||
return $this->ProductCategories
|
||||
->find('path', for: $category->id)
|
||||
->contain(['ProductCategoryVariants']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $internalCategoryId
|
||||
* @return array
|
||||
*/
|
||||
public function getAllCategoryVariantsForCategoryId(string $internalCategoryId)
|
||||
{
|
||||
return $this->find('allCategoryVariantsForCategoryId', $internalCategoryId)->toArray();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,6 +63,14 @@ class ProductsTable extends Table
|
|||
'dependent' => true,
|
||||
'cascadeCallbacks' => true,
|
||||
]);
|
||||
|
||||
$this->hasMany('ProductCategoryVariants', [
|
||||
'foreignKey' => 'product_id',
|
||||
'className' => 'CakeProducts.ProductCategoryVariants',
|
||||
'dependent' => true,
|
||||
'cascadeCallbacks' => true,
|
||||
]);
|
||||
|
||||
$this->getSchema()->setColumnType('product_type_id', EnumType::from(ProductProductTypeId::class));
|
||||
|
||||
$this->addBehavior('Muffin/Trash.Trash');
|
||||
|
|
|
@ -25,3 +25,4 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?= $this->Html->script('CakeProducts.product_category_variant_options.js'); ?>
|
||||
|
|
|
@ -30,3 +30,4 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?= $this->Html->script('CakeProducts.product_category_variant_options.js'); ?>
|
||||
|
|
|
@ -8,3 +8,6 @@ echo $this->Form->control('product_category_id', ['options' => $productCategorie
|
|||
echo $this->Form->control('product_id', ['options' => $products, 'empty' => true]);
|
||||
echo $this->Form->control('enabled');
|
||||
?>
|
||||
<legend><?= __('Variant Options') . '<small class="ms-2">' . $this->Html->link('Add Option', '#', [
|
||||
'id' => 'add-option-button',
|
||||
]) . '</small>'; ?></legend>
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
/**
|
||||
* @var \App\View\AppView $this
|
||||
* @var \Cake\Datasource\EntityInterface $productCategoryAttributeOption
|
||||
*/
|
||||
$cnt = 0;
|
||||
$prefix = $prefix ?? '';
|
||||
\Cake\Log\Log::debug('$prefix');
|
||||
\Cake\Log\Log::debug($prefix);
|
||||
?>
|
||||
<div class="container product-category-attribute-options-container" data-test="1" data-prefix="<?= $prefix; ?>">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<?= $this->Form->control($prefix . 'attribute_value', [
|
||||
'label' => 'Value',
|
||||
]); ?>
|
||||
</div>
|
||||
<div class="col">
|
||||
<?= $this->Form->control($prefix . 'attribute_label', [
|
||||
'label' => 'Label',
|
||||
]); ?>
|
||||
</div>
|
||||
<div class="col">
|
||||
<?= $this->Form->control($prefix . 'enabled', [
|
||||
'type' => 'checkbox',
|
||||
'checked' => true,
|
||||
'label' => 'Enabled',
|
||||
]); ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CakeProducts\Test\Fixture;
|
||||
|
||||
use Cake\TestSuite\Fixture\TestFixture;
|
||||
|
||||
/**
|
||||
* ProductCategoryVariantsFixture
|
||||
*/
|
||||
class ProductCategoryVariantOptionsFixture extends TestFixture
|
||||
{
|
||||
/**
|
||||
* Init method
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init(): void
|
||||
{
|
||||
$this->records = [
|
||||
[
|
||||
'id' => '5a386e9f-6e7a-4ae7-9360-c8e529f78d93',
|
||||
'variant_value' => 'Color',
|
||||
'variant_label' => null,
|
||||
'product_category_variant_id' => '5a386e9f-6e7a-4ae7-9360-c8e529f78d93',
|
||||
'created' => '2025-07-04 12:00:00',
|
||||
'modified' => '2025-07-04 12:00:00',
|
||||
'enabled' => 1,
|
||||
],
|
||||
[
|
||||
'id' => '5a386e9f-6e7a-4ae7-9360-c8e529f78d94',
|
||||
'variant_value' => 'Size',
|
||||
'variant_label' => null,
|
||||
'product_category_variant_id' => '5a386e9f-6e7a-4ae7-9360-c8e529f78d93',
|
||||
'created' => '2025-07-04 12:00:00',
|
||||
'modified' => '2025-07-04 12:00:00',
|
||||
'enabled' => 1,
|
||||
],
|
||||
];
|
||||
parent::init();
|
||||
}
|
||||
}
|
|
@ -67,6 +67,7 @@ class ProductCategoriesTableTest extends TestCase
|
|||
'ChildProductCategories',
|
||||
// 'Products',
|
||||
'ProductCategoryAttributes',
|
||||
'ProductCategoryVariants',
|
||||
];
|
||||
$associations = $this->ProductCategories->associations();
|
||||
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CakeProducts\Test\TestCase\Model\Table;
|
||||
|
||||
use CakeProducts\Model\Table\ProductCategoryVariantOptionsTable;
|
||||
use Cake\TestSuite\TestCase;
|
||||
|
||||
/**
|
||||
* App\Model\Table\ProductCategoryVariantOptionsTable Test Case
|
||||
*/
|
||||
class ProductCategoryVariantOptionsTableTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* Test subject
|
||||
*
|
||||
* @var \App\Model\Table\ProductCategoryVariantOptionsTable
|
||||
*/
|
||||
protected $ProductCategoryVariantOptions;
|
||||
|
||||
/**
|
||||
* Fixtures
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected array $fixtures = [
|
||||
// 'ProductCategoryVariantOptions',
|
||||
// 'ProductCategoryVariants',
|
||||
];
|
||||
|
||||
/**
|
||||
* setUp method
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$config = $this->getTableLocator()->exists('ProductCategoryVariantOptions') ? [] : ['className' => ProductCategoryVariantOptionsTable::class];
|
||||
$this->ProductCategoryVariantOptions = $this->getTableLocator()->get('ProductCategoryVariantOptions', $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* tearDown method
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function tearDown(): void
|
||||
{
|
||||
unset($this->ProductCategoryVariantOptions);
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* TestInitialize method
|
||||
*
|
||||
* @return void
|
||||
* @uses \App\Model\Table\ProductCategoryVariantOptionsTable::initialize()
|
||||
*/
|
||||
public function testInitialize(): void
|
||||
{
|
||||
// verify all associations loaded
|
||||
$expectedAssociations = [
|
||||
'ProductCategoryVariants',
|
||||
];
|
||||
$associations = $this->ProductCategoryVariantOptions->associations();
|
||||
|
||||
$this->assertCount(count($expectedAssociations), $associations);
|
||||
foreach ($expectedAssociations as $expectedAssociation) {
|
||||
$this->assertTrue($this->ProductCategoryVariantOptions->hasAssociation($expectedAssociation));
|
||||
}
|
||||
|
||||
// verify all behaviors loaded
|
||||
$expectedBehaviors = [
|
||||
'Timestamp',
|
||||
];
|
||||
$behaviors = $this->ProductCategoryVariantOptions->behaviors();
|
||||
|
||||
$this->assertCount(count($expectedBehaviors), $behaviors);
|
||||
foreach ($expectedBehaviors as $expectedBehavior) {
|
||||
$this->assertTrue($this->ProductCategoryVariantOptions->hasBehavior($expectedBehavior));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test validationDefault method
|
||||
*
|
||||
* @return void
|
||||
* @uses \App\Model\Table\ProductCategoryVariantOptionsTable::validationDefault()
|
||||
*/
|
||||
public function testValidationDefault(): void
|
||||
{
|
||||
$this->markTestIncomplete('Not implemented yet.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test buildRules method
|
||||
*
|
||||
* @return void
|
||||
* @uses \App\Model\Table\ProductCategoryVariantOptionsTable::buildRules()
|
||||
*/
|
||||
public function testBuildRules(): void
|
||||
{
|
||||
$this->markTestIncomplete('Not implemented yet.');
|
||||
}
|
||||
}
|
|
@ -64,6 +64,7 @@ class ProductsTableTest extends TestCase
|
|||
$expectedAssociations = [
|
||||
'ProductCategories',
|
||||
'ProductAttributes',
|
||||
'ProductCategoryVariants',
|
||||
];
|
||||
$associations = $this->Products->associations();
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
const addOptionButton = document.getElementById('add-option-button');
|
||||
const variantOptionPrefixInput = document.getElementById('variant_options_prefix');
|
||||
if (addOptionButton && variantOptionPrefixInput) {
|
||||
addOptionButton.addEventListener('click', addOptionButtonClicked);
|
||||
}
|
||||
function addOptionButtonClicked(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
console.debug('variantOptionPrefixInput.value');
|
||||
console.debug(variantOptionPrefixInput.value);
|
||||
variantOptionPrefixInput.value = parseInt(variantOptionPrefixInput.value) + 1;
|
||||
variantOptionPrefixInput.dispatchEvent(new Event('change'));
|
||||
console.debug('variantOptionPrefixInput.value');
|
||||
console.debug(variantOptionPrefixInput.value);
|
||||
}
|
Loading…
Reference in New Issue