bring into standalone plugin for distribution

This commit is contained in:
2024-11-24 18:38:29 -08:00
parent 279d46f14b
commit ded60d16bf
83 changed files with 7020 additions and 1 deletions

View File

@@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
namespace CakeProducts;
use Cake\Console\CommandCollection;
use Cake\Core\BasePlugin;
use Cake\Core\ContainerInterface;
use Cake\Core\PluginApplicationInterface;
use Cake\Http\MiddlewareQueue;
use Cake\Routing\RouteBuilder;
use CakeProducts\Service\CatalogManagerServiceProvider;
/**
* Plugin for CakeProducts
*/
class CakeProductsPlugin extends BasePlugin
{
/**
* Load all the plugin configuration and bootstrap logic.
*
* The host application is provided as an argument. This allows you to load
* additional plugin dependencies, or attach events.
*
* @param \Cake\Core\PluginApplicationInterface $app The host application
* @return void
*/
public function bootstrap(PluginApplicationInterface $app): void
{
}
/**
* Add routes for the plugin.
*
* If your plugin has many routes and you would like to isolate them into a separate file,
* you can create `$plugin/config/routes.php` and delete this method.
*
* @param \Cake\Routing\RouteBuilder $routes The route builder to update.
* @return void
*/
public function routes(RouteBuilder $routes): void
{
$routes->plugin(
'CakeProducts',
['path' => '/cake-products'],
function (RouteBuilder $builder) {
// Add custom routes here
$builder->fallbacks();
}
);
parent::routes($routes);
}
/**
* Add middleware for the plugin.
*
* @param \Cake\Http\MiddlewareQueue $middlewareQueue The middleware queue to update.
* @return \Cake\Http\MiddlewareQueue
*/
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
// Add your middlewares here
return $middlewareQueue;
}
/**
* Add commands for the plugin.
*
* @param \Cake\Console\CommandCollection $commands The command collection to update.
* @return \Cake\Console\CommandCollection
*/
public function console(CommandCollection $commands): CommandCollection
{
// Add your commands here
$commands = parent::console($commands);
return $commands;
}
/**
* Register application container services.
*
* @param \Cake\Core\ContainerInterface $container The Container to update.
* @return void
* @link https://book.cakephp.org/4/en/development/dependency-injection.html#dependency-injection
*/
public function services(ContainerInterface $container): void
{
// Add your services here
$container->addServiceProvider(new CatalogManagerServiceProvider());
}
}

View File

@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace CakeProducts\Controller;
use App\Controller\AppController as BaseController;
class AppController extends BaseController
{
}

View File

@@ -0,0 +1,110 @@
<?php
declare(strict_types=1);
namespace CakeProducts\Controller;
use Cake\Log\Log;
use CakeProducts\Controller\AppController;
/**
* ExternalProductCatalogs Controller
*
* @property \CakeProducts\Model\Table\ExternalProductCatalogsTable $ExternalProductCatalogs
*/
class ExternalProductCatalogsController extends AppController
{
/**
* Index method
*
* @return \Cake\Http\Response|null|void Renders view
*/
public function index()
{
$query = $this->ExternalProductCatalogs->find()
->contain(['ProductCatalogs']);
$externalProductCatalogs = $this->paginate($query);
$this->set(compact('externalProductCatalogs'));
}
/**
* View method
*
* @param string|null $id External Product Catalog id.
* @return \Cake\Http\Response|null|void Renders view
* @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
*/
public function view($id = null)
{
$externalProductCatalog = $this->ExternalProductCatalogs->get($id, contain: ['ProductCatalogs']);
$this->set(compact('externalProductCatalog'));
}
/**
* Add method
*
* @return \Cake\Http\Response|null|void Redirects on successful add, renders view otherwise.
*/
public function add()
{
$externalProductCatalog = $this->ExternalProductCatalogs->newEmptyEntity();
if ($this->request->is('post')) {
$externalProductCatalog = $this->ExternalProductCatalogs->patchEntity($externalProductCatalog, $this->request->getData());
if ($this->ExternalProductCatalogs->save($externalProductCatalog)) {
$this->Flash->success(__('The external product catalog has been saved.'));
return $this->redirect(['action' => 'index']);
}
Log::debug(print_r('$externalProductCatalog->getErrors() next - failed /add', true));
Log::debug(print_r($externalProductCatalog->getErrors(), true));
$this->Flash->error(__('The external product catalog could not be saved. Please, try again.'));
}
$productCatalogs = $this->ExternalProductCatalogs->ProductCatalogs->find('list', limit: 200)->all();
$this->set(compact('externalProductCatalog', 'productCatalogs'));
}
/**
* Edit method
*
* @param string|null $id External Product Catalog id.
* @return \Cake\Http\Response|null|void Redirects on successful edit, renders view otherwise.
* @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
*/
public function edit($id = null)
{
$externalProductCatalog = $this->ExternalProductCatalogs->get($id, contain: []);
if ($this->request->is(['patch', 'post', 'put'])) {
$externalProductCatalog = $this->ExternalProductCatalogs->patchEntity($externalProductCatalog, $this->request->getData());
if ($this->ExternalProductCatalogs->save($externalProductCatalog)) {
$this->Flash->success(__('The external product catalog has been saved.'));
return $this->redirect(['action' => 'index']);
}
Log::debug(print_r('$externalProductCatalog->getErrors() next - failed /edit', true));
Log::debug(print_r($externalProductCatalog->getErrors(), true));
$this->Flash->error(__('The external product catalog could not be saved. Please, try again.'));
}
$productCatalogs = $this->ExternalProductCatalogs->ProductCatalogs->find('list', limit: 200)->all();
$this->set(compact('externalProductCatalog', 'productCatalogs'));
}
/**
* Delete method
*
* @param string|null $id External Product Catalog id.
* @return \Cake\Http\Response|null Redirects to index.
* @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
*/
public function delete($id = null)
{
$this->request->allowMethod(['post', 'delete']);
$externalProductCatalog = $this->ExternalProductCatalogs->get($id);
if ($this->ExternalProductCatalogs->delete($externalProductCatalog)) {
$this->Flash->success(__('The external product catalog has been deleted.'));
} else {
$this->Flash->error(__('The external product catalog could not be deleted. Please, try again.'));
}
return $this->redirect(['action' => 'index']);
}
}

View File

@@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
namespace CakeProducts\Controller;
use Cake\Datasource\Exception\RecordNotFoundException;
use Cake\Http\Response;
use Cake\Log\Log;
use CakeProducts\Controller\AppController;
use CakeProducts\Model\Table\ProductCatalogsTable;
use CakeProducts\Service\InternalCatalogManagerService;
/**
* ProductCatalogs Controller
*
* @property ProductCatalogsTable $ProductCatalogs
*/
class ProductCatalogsController extends AppController
{
/**
* Index method
*
* @return Response|null|void Renders view
*/
public function index()
{
$query = $this->ProductCatalogs->find();
$productCatalogs = $this->paginate($query);
$this->set(compact('productCatalogs'));
}
/**
* View method
*
* @param string|null $id Product Catalog id.
* @return Response|null|void Renders view
* @throws RecordNotFoundException When record not found.
*/
public function view(InternalCatalogManagerService $catalogManagerService, $id = null)
{
$productCatalog = $catalogManagerService->getCatalog($id);
$this->set(compact('productCatalog'));
}
/**
* Add method
*
* @return Response|null|void Redirects on successful add, renders view otherwise.
*/
public function add()
{
$productCatalog = $this->ProductCatalogs->newEmptyEntity();
if ($this->request->is('post')) {
$productCatalog = $this->ProductCatalogs->patchEntity($productCatalog, $this->request->getData());
if ($this->ProductCatalogs->save($productCatalog)) {
$this->Flash->success(__('The product catalog has been saved.'));
return $this->redirect(['action' => 'index']);
}
Log::debug('failed to save new product catalog errors next');
Log::debug(print_r('$productCatalog->getErrors()', true));
Log::debug(print_r($productCatalog->getErrors(), true));
$this->Flash->error(__('The product catalog could not be saved. Please, try again.'));
}
$this->set(compact('productCatalog'));
}
/**
* Edit method
*
* @param string|null $id Product Catalog id.
* @return Response|null|void Redirects on successful edit, renders view otherwise.
* @throws RecordNotFoundException When record not found.
*/
public function edit($id = null)
{
$productCatalog = $this->ProductCatalogs->get($id, contain: []);
if ($this->request->is(['patch', 'post', 'put'])) {
$productCatalog = $this->ProductCatalogs->patchEntity($productCatalog, $this->request->getData());
if ($this->ProductCatalogs->save($productCatalog)) {
$this->Flash->success(__('The product catalog has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('The product catalog could not be saved. Please, try again.'));
}
$this->set(compact('productCatalog'));
}
/**
* Delete method
*
* @param string|null $id Product Catalog id.
* @return Response|null Redirects to index.
* @throws RecordNotFoundException When record not found.
*/
public function delete($id = null)
{
$this->request->allowMethod(['post', 'delete']);
$productCatalog = $this->ProductCatalogs->get($id);
if ($this->ProductCatalogs->delete($productCatalog)) {
$this->Flash->success(__('The product catalog has been deleted.'));
} else {
$this->Flash->error(__('The product catalog could not be deleted. Please, try again.'));
}
return $this->redirect(['action' => 'index']);
}
}

View File

@@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
namespace CakeProducts\Controller;
use Cake\Log\Log;
use CakeProducts\Controller\AppController;
use CakeProducts\Service\InternalCatalogManagerService;
/**
* ProductCategories Controller
*
* @property \CakeProducts\Model\Table\ProductCategoriesTable $ProductCategories
*/
class ProductCategoriesController extends AppController
{
/**
* Index method
*
* @return \Cake\Http\Response|null|void Renders view
*/
public function index()
{
$query = $this->ProductCategories->find()
->contain(['ProductCatalogs', 'ParentProductCategories']);
$productCategories = $this->paginate($query);
$this->set(compact('productCategories'));
}
/**
* View method
*
* @param string|null $id Product Category id.
* @return \Cake\Http\Response|null|void Renders view
* @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
*/
public function view(InternalCatalogManagerService $catalogManagerService, $id = null)
{
$productCategory = $catalogManagerService->getCategory($id);
$this->set(compact('productCategory'));
}
/**
* Add method
*
* @return \Cake\Http\Response|null|void Redirects on successful add, renders view otherwise.
*/
public function add(InternalCatalogManagerService $catalogManagerService)
{
$productCategory = $this->ProductCategories->newEmptyEntity();
if ($this->request->is('post')) {
$postData = $this->request->getData();
if ($this->request->getSession()->read('Auth.User.id')) {
$postData['created_by'] = $this->request->getSession()->read('Auth.User.id');
}
$result = $catalogManagerService->createNewCategory($productCategory, $postData);
Log::debug(print_r('$result from createNewCategory', true));
Log::debug(print_r($result, true));
if ($result['result']) {
$this->Flash->success(__('The product category has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('The product category could not be saved. Please, try again.'));
}
$productCatalogs = $this->ProductCategories->ProductCatalogs->find('list', limit: 200)->all();
$parentProductCategories = $this->ProductCategories->ParentProductCategories->find('list', limit: 200)->all();
$this->set(compact('productCategory', 'productCatalogs', 'parentProductCategories'));
}
/**
* Edit method
*
* @param string|null $id Product Category id.
* @return \Cake\Http\Response|null|void Redirects on successful edit, renders view otherwise.
* @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
*/
public function edit(InternalCatalogManagerService $catalogManagerService, $id = null)
{
$productCategory = $this->ProductCategories->get($id, contain: []);
if ($this->request->is(['patch', 'post', 'put'])) {
$productCategory = $this->ProductCategories->patchEntity($productCategory, $this->request->getData());
if ($this->ProductCategories->save($productCategory)) {
$this->Flash->success(__('The product category has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('The product category could not be saved. Please, try again.'));
}
$productCatalogs = $this->ProductCategories->ProductCatalogs->find('list', limit: 200)->all();
$parentProductCategories = $this->ProductCategories->ParentProductCategories->find('list', limit: 200)->all();
$this->set(compact('productCategory', 'productCatalogs', 'parentProductCategories'));
}
/**
* Delete method
*
* @param string|null $id Product Category id.
* @return \Cake\Http\Response|null Redirects to index.
* @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
*/
public function delete($id = null)
{
$this->request->allowMethod(['post', 'delete']);
$productCategory = $this->ProductCategories->get($id);
if ($this->ProductCategories->delete($productCategory)) {
$this->Flash->success(__('The product category has been deleted.'));
} else {
$this->Flash->error(__('The product category could not be deleted. Please, try again.'));
}
return $this->redirect(['action' => 'index']);
}
}

View File

@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace CakeProducts\Controller;
use Cake\Log\Log;
use CakeProducts\Controller\AppController;
/**
* ProductCategoryAttributeOptions Controller
*
* @property \CakeProducts\Model\Table\ProductCategoryAttributeOptionsTable $ProductCategoryAttributeOptions
*/
class ProductCategoryAttributeOptionsController extends AppController
{
/**
* Add method
*
* @return \Cake\Http\Response|null|void Redirects on successful add, renders view otherwise.
*/
public function add()
{
Log::debug('inside product category attribute options controller add');
$productCategoryAttributeOption = $this->ProductCategoryAttributeOptions->newEmptyEntity();
$this->set(compact('productCategoryAttributeOption'));
}
/**
* Delete method
*
* @param string|null $id Product Category Attribute Option id.
* @return \Cake\Http\Response|null Redirects to index.
* @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
*/
public function delete($id = null)
{
$this->request->allowMethod(['post', 'delete']);
$productCategoryAttributeOption = $this->ProductCategoryAttributeOptions->get($id);
if ($this->ProductCategoryAttributeOptions->delete($productCategoryAttributeOption)) {
$this->Flash->success(__('The product category attribute option has been deleted.'));
} else {
$this->Flash->error(__('The product category attribute option could not be deleted. Please, try again.'));
}
return $this->redirect(['controller' => 'ProductCategoryAttributes', 'action' => 'view', $productCategoryAttributeOption->product_category_attribute_id]);
}
}

View File

@@ -0,0 +1,144 @@
<?php
declare(strict_types=1);
namespace CakeProducts\Controller;
use Cake\Datasource\Exception\RecordNotFoundException;
use Cake\Http\Response;
use Cake\Log\Log;
use CakeProducts\Controller\AppController;
use CakeProducts\Model\Enum\ProductCategoryAttributeTypeId;
use CakeProducts\Model\Table\ProductCategoryAttributesTable;
/**
* ProductCategoryAttributes Controller
*
* @property ProductCategoryAttributesTable $ProductCategoryAttributes
*/
class ProductCategoryAttributesController extends AppController
{
/**
* Index method
*
* @return Response|null|void Renders view
*/
public function index()
{
$query = $this->ProductCategoryAttributes->find()
->contain(['ProductCategories']);
$productCategoryAttributes = $this->paginate($query);
$this->set(compact('productCategoryAttributes'));
}
/**
* View method
*
* @param string|null $id Product Category Attribute id.
* @return Response|null|void Renders view
* @throws RecordNotFoundException When record not found.
*/
public function view($id = null)
{
$productCategoryAttribute = $this->ProductCategoryAttributes->get($id, contain: [
'ProductCategories',
'ProductCategoryAttributeOptions',
]);
$this->set(compact('productCategoryAttribute'));
}
/**
* Add method
*
* @return Response|null|void Redirects on successful add, renders view otherwise.
*/
public function add()
{
$productCategoryAttribute = $this->ProductCategoryAttributes->newEmptyEntity();
if ($this->request->is('post')) {
$postData = $this->request->getData();
$saveOptions = [
'associated' => ['ProductCategoryAttributeOptions'],
];
Log::debug(print_r('$postData', true));
Log::debug(print_r($postData, true));
// if ($this->request->getData('attribute_type_id') != ProductCategoryAttributeTypeId::Constrained) {
// $saveOptions['associated'] = [];
// $postData['product_category_attribute_options'] = [];
// }
Log::debug(print_r('$postData', true));
Log::debug(print_r($postData, true));
$productCategoryAttribute = $this->ProductCategoryAttributes->patchEntity($productCategoryAttribute, $postData, $saveOptions);
if ($this->ProductCategoryAttributes->save($productCategoryAttribute, $saveOptions)) {
$this->Flash->success(__('The product category attribute has been saved.'));
return $this->redirect(['action' => 'index']);
}
Log::debug('failed to save new product category attribute errors next');
Log::debug(print_r('$productCategoryAttribute->getErrors()', true));
Log::debug(print_r($productCategoryAttribute->getErrors(), true));
$this->Flash->error(__('The product category attribute could not be saved. Please, try again.'));
}
$productCategories = $this->ProductCategoryAttributes->ProductCategories->find('list', limit: 200)->all();
$this->set(compact('productCategoryAttribute', 'productCategories'));
}
/**
* Edit method
*
* @param string|null $id Product Category Attribute id.
* @return Response|null|void Redirects on successful edit, renders view otherwise.
* @throws RecordNotFoundException When record not found.
*/
public function edit($id = null)
{
$productCategoryAttribute = $this->ProductCategoryAttributes->get($id, contain: ['ProductCategoryAttributeOptions']);
if ($this->request->is(['patch', 'post', 'put'])) {
$postData = $this->request->getData();
$saveOptions = [
'associated' => ['ProductCategoryAttributeOptions'],
];
Log::debug(print_r('$postData', true));
Log::debug(print_r($postData, true));
// if ($this->request->getData('attribute_type_id') != ProductCategoryAttributeTypeId::Constrained) {
// $saveOptions['associated'] = [];
// $postData['product_category_attribute_options'] = [];
// }
Log::debug(print_r('$postData', true));
Log::debug(print_r($postData, true));
$productCategoryAttribute = $this->ProductCategoryAttributes->patchEntity($productCategoryAttribute, $postData, $saveOptions);
if ($this->ProductCategoryAttributes->save($productCategoryAttribute, $saveOptions)) {
$this->Flash->success(__('The product category attribute has been saved.'));
return $this->redirect(['action' => 'index']);
}
Log::debug('failed to save product category attribute on edit errors next');
Log::debug(print_r('$productCategoryAttribute->getErrors()', true));
Log::debug(print_r($productCategoryAttribute->getErrors(), true));
$this->Flash->error(__('The product category attribute could not be saved. Please, try again.'));
}
$productCategories = $this->ProductCategoryAttributes->ProductCategories->find('list', limit: 200)->all();
$this->set(compact('productCategoryAttribute', 'productCategories'));
}
/**
* Delete method
*
* @param string|null $id Product Category Attribute id.
* @return Response|null Redirects to index.
* @throws RecordNotFoundException When record not found.
*/
public function delete($id = null)
{
$this->request->allowMethod(['post', 'delete']);
$productCategoryAttribute = $this->ProductCategoryAttributes->get($id);
if ($this->ProductCategoryAttributes->delete($productCategoryAttribute)) {
$this->Flash->success(__('The product category attribute has been deleted.'));
} else {
$this->Flash->error(__('The product category attribute could not be deleted. Please, try again.'));
}
return $this->redirect(['action' => 'index']);
}
}

View File

@@ -0,0 +1,110 @@
<?php
declare(strict_types=1);
namespace CakeProducts\Controller;
use Cake\Log\Log;
use CakeProducts\Controller\AppController;
/**
* Products Controller
*
* @property \CakeProducts\Model\Table\ProductsTable $Products
*/
class ProductsController extends AppController
{
/**
* Index method
*
* @return \Cake\Http\Response|null|void Renders view
*/
public function index()
{
$query = $this->Products->find()
->contain(['ProductCategories']);
$products = $this->paginate($query);
$this->set(compact('products'));
}
/**
* View method
*
* @param string|null $id Product id.
* @return \Cake\Http\Response|null|void Renders view
* @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
*/
public function view($id = null)
{
$product = $this->Products->get($id, contain: ['ProductCategories']);
$this->set(compact('product'));
}
/**
* Add method
*
* @return \Cake\Http\Response|null|void Redirects on successful add, renders view otherwise.
*/
public function add()
{
$product = $this->Products->newEmptyEntity();
if ($this->request->is('post')) {
$product = $this->Products->patchEntity($product, $this->request->getData());
if ($this->Products->save($product)) {
$this->Flash->success(__('The product has been saved.'));
return $this->redirect(['action' => 'index']);
}
Log::debug(print_r('$product->getErrors() next - failed in products/add', true));
Log::debug(print_r($product->getErrors(), true));
$this->Flash->error(__('The product could not be saved. Please, try again.'));
}
$productCategories = $this->Products->ProductCategories->find('list', limit: 200)->all();
$this->set(compact('product', 'productCategories'));
}
/**
* Edit method
*
* @param string|null $id Product id.
* @return \Cake\Http\Response|null|void Redirects on successful edit, renders view otherwise.
* @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
*/
public function edit($id = null)
{
$product = $this->Products->get($id, contain: []);
if ($this->request->is(['patch', 'post', 'put'])) {
$product = $this->Products->patchEntity($product, $this->request->getData());
if ($this->Products->save($product)) {
$this->Flash->success(__('The product has been saved.'));
return $this->redirect(['action' => 'index']);
}
Log::debug(print_r('$product->getErrors() next - failed in products/edit', true));
Log::debug(print_r($product->getErrors(), true));
$this->Flash->error(__('The product could not be saved. Please, try again.'));
}
$productCategories = $this->Products->ProductCategories->find('list', limit: 200)->all();
$this->set(compact('product', 'productCategories'));
}
/**
* Delete method
*
* @param string|null $id Product id.
* @return \Cake\Http\Response|null Redirects to index.
* @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
*/
public function delete($id = null)
{
$this->request->allowMethod(['post', 'delete']);
$product = $this->Products->get($id);
if ($this->Products->delete($product)) {
$this->Flash->success(__('The product has been deleted.'));
} else {
$this->Flash->error(__('The product could not be deleted. Please, try again.'));
}
return $this->redirect(['action' => 'index']);
}
}

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace CakeProducts\Model\Entity;
use Cake\ORM\Entity;
/**
* ExternalProductCatalog Entity
*
* @property int $id
* @property string $product_catalog_id
* @property string $base_url
* @property string $api_url
* @property \Cake\I18n\DateTime $created
* @property \Cake\I18n\DateTime|null $deleted
* @property bool $enabled
*
* @property \CakeProducts\Model\Entity\ProductCatalog $product_catalog
*/
class ExternalProductCatalog 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_catalog_id' => true,
'base_url' => true,
'api_url' => true,
'created' => true,
'deleted' => true,
'enabled' => true,
'product_catalog' => true,
];
}

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace CakeProducts\Model\Entity;
use Cake\ORM\Entity;
/**
* Product Entity
*
* @property string $id
* @property string $name
* @property string $product_category_id
* @property \CakeProducts\Model\Enum\ProductProductTypeId $product_type_id
*
* @property \CakeProducts\Model\Entity\ProductCategory $product_category
*/
class Product 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 = [
'name' => true,
'product_category_id' => true,
'product_type_id' => true,
'product_category' => true,
];
}

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace CakeProducts\Model\Entity;
use Cake\ORM\Entity;
/**
* ProductCatalog Entity
*
* @property string $id
* @property string $name
* @property string|null $catalog_description
* @property bool $enabled
*
* @property \CakeProducts\Model\Entity\ProductCategory[] $product_categories
* @property \CakeProducts\Model\Entity\ExternalProductCatalog[] $external_product_catalogs
*/
class ProductCatalog 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 = [
'name' => true,
'catalog_description' => true,
'enabled' => true,
'product_categories' => true,
'external_product_catalogs' => true,
];
}

View File

@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace CakeProducts\Model\Entity;
use Cake\ORM\Entity;
/**
* ProductCategory Entity
*
* @property int $id
* @property string $internal_id
* @property string $product_catalog_id
* @property string $name
* @property string|null $category_description
* @property int|null $parent_id
* @property int $lft
* @property int $rght
* @property bool $enabled
*
* @property \CakeProducts\Model\Entity\ProductCatalog $product_catalog
* @property \CakeProducts\Model\Entity\ParentProductCategory $parent_product_category
* @property \CakeProducts\Model\Entity\ChildProductCategory[] $child_product_categories
*/
class ProductCategory 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_catalog_id' => true,
'internal_id' => true,
'name' => true,
'category_description' => true,
'parent_id' => true,
'lft' => true,
'rght' => true,
'enabled' => true,
'product_catalog' => true,
'parent_product_category' => true,
'child_product_categories' => true,
];
}

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace CakeProducts\Model\Entity;
use Cake\ORM\Entity;
/**
* ProductCategoryAttribute Entity
*
* @property string $id
* @property string $name
* @property string|null $product_category_id
* @property int $attribute_type_id
* @property bool $enabled
*
* @property ProductCategory $product_category
* @property ProductCategoryAttributeOption[] $product_category_attribute_options
*/
class ProductCategoryAttribute 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 = [
'name' => true,
'product_category_id' => true,
'attribute_type_id' => true,
'enabled' => true,
'product_category' => true,
'product_category_attribute_options' => true,
];
}

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace CakeProducts\Model\Entity;
use Cake\ORM\Entity;
/**
* ProductCategoryAttributeOption Entity
*
* @property string $id
* @property string $product_category_attribute_id
* @property string $attribute_value
* @property string $attribute_label
* @property bool $enabled
*
* @property ProductCategoryAttribute $product_category_attribute
*/
class ProductCategoryAttributeOption 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_attribute_id' => true,
'attribute_value' => true,
'attribute_label' => true,
'enabled' => true,
'product_category_attribute' => true,
];
}

View File

@@ -0,0 +1,23 @@
<?php
namespace CakeProducts\Model\Enum;
use Cake\Database\Type\EnumLabelInterface;
use Tools\Model\Enum\EnumOptionsTrait;
enum ProductCategoryAttributeTypeId: int implements EnumLabelInterface
{
use EnumOptionsTrait;
case Constrained = 1;
case Text = 2;
case Integer = 3;
public function label(): string
{
return match($this) {
self::Constrained => 'Constrained',
self::Text => 'Text',
self::Integer => 'Integer'
};
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace CakeProducts\Model\Enum;
use Cake\Database\Type\EnumLabelInterface;
use Tools\Model\Enum\EnumOptionsTrait;
enum ProductProductTypeId: int implements EnumLabelInterface
{
use EnumOptionsTrait;
case Service = 1;
case Product = 2;
case Consumable = 3;
public function label(): string
{
return match($this) {
self::Service => 'Service',
self::Product => 'Product',
self::Consumable => 'Consumable'
};
}
}

View File

@@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
namespace CakeProducts\Model\Table;
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 CakeProducts\Model\Entity\ExternalProductCatalog;
use Closure;
use Psr\SimpleCache\CacheInterface;
/**
* ExternalProductCatalogs Model
*
* @property ProductCatalogsTable&BelongsTo $ProductCatalogs
*
* @method ExternalProductCatalog newEmptyEntity()
* @method ExternalProductCatalog newEntity(array $data, array $options = [])
* @method array<ExternalProductCatalog> newEntities(array $data, array $options = [])
* @method ExternalProductCatalog get(mixed $primaryKey, array|string $finder = 'all', CacheInterface|string|null $cache = null, Closure|string|null $cacheKey = null, mixed ...$args)
* @method ExternalProductCatalog findOrCreate($search, ?callable $callback = null, array $options = [])
* @method ExternalProductCatalog patchEntity(EntityInterface $entity, array $data, array $options = [])
* @method array<ExternalProductCatalog> patchEntities(iterable $entities, array $data, array $options = [])
* @method ExternalProductCatalog|false save(EntityInterface $entity, array $options = [])
* @method ExternalProductCatalog saveOrFail(EntityInterface $entity, array $options = [])
* @method iterable<ExternalProductCatalog>|ResultSetInterface<ExternalProductCatalog>|false saveMany(iterable $entities, array $options = [])
* @method iterable<ExternalProductCatalog>|ResultSetInterface<ExternalProductCatalog> saveManyOrFail(iterable $entities, array $options = [])
* @method iterable<ExternalProductCatalog>|ResultSetInterface<ExternalProductCatalog>|false deleteMany(iterable $entities, array $options = [])
* @method iterable<ExternalProductCatalog>|ResultSetInterface<ExternalProductCatalog> deleteManyOrFail(iterable $entities, array $options = [])
*
* @mixin TimestampBehavior
*/
class ExternalProductCatalogsTable 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('external_product_catalogs');
$this->setDisplayField('base_url');
$this->setPrimaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsTo('ProductCatalogs', [
'foreignKey' => 'product_catalog_id',
'joinType' => 'INNER',
'className' => 'CakeProducts.ProductCatalogs',
]);
}
/**
* Default validation rules.
*
* @param Validator $validator Validator instance.
* @return Validator
*/
public function validationDefault(Validator $validator): Validator
{
$validator
->uuid('product_catalog_id')
->notEmptyString('product_catalog_id');
$validator
->scalar('base_url')
->maxLength('base_url', 255)
->requirePresence('base_url', 'create')
->notEmptyString('base_url');
// ->url('base_url');
$validator
->scalar('api_url')
->maxLength('api_url', 255)
->requirePresence('api_url', 'create')
->notEmptyString('api_url');
// ->url('api_url');
$validator
->dateTime('deleted')
->allowEmptyDateTime('deleted');
$validator
->boolean('enabled')
->requirePresence('enabled', 'create')
->notEmptyString('enabled');
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_catalog_id'], 'ProductCatalogs'), ['errorField' => 'product_catalog_id']);
return $rules;
}
}

View File

@@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
namespace CakeProducts\Model\Table;
use Cake\Core\Configure;
use Cake\Datasource\EntityInterface;
use Cake\Datasource\ResultSetInterface;
use Cake\ORM\Query\SelectQuery;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use CakeProducts\Model\Entity\ProductCatalog;
use Closure;
use Psr\SimpleCache\CacheInterface;
/**
* ProductCatalogs Model
*
* @method ProductCatalog newEmptyEntity()
* @method ProductCatalog newEntity(array $data, array $options = [])
* @method array<ProductCatalog> newEntities(array $data, array $options = [])
* @method ProductCatalog get(mixed $primaryKey, array|string $finder = 'all', CacheInterface|string|null $cache = null, Closure|string|null $cacheKey = null, mixed ...$args)
* @method ProductCatalog findOrCreate($search, ?callable $callback = null, array $options = [])
* @method ProductCatalog patchEntity(EntityInterface $entity, array $data, array $options = [])
* @method array<ProductCatalog> patchEntities(iterable $entities, array $data, array $options = [])
* @method ProductCatalog|false save(EntityInterface $entity, array $options = [])
* @method ProductCatalog saveOrFail(EntityInterface $entity, array $options = [])
* @method iterable<ProductCatalog>|ResultSetInterface<ProductCatalog>|false saveMany(iterable $entities, array $options = [])
* @method iterable<ProductCatalog>|ResultSetInterface<ProductCatalog> saveManyOrFail(iterable $entities, array $options = [])
* @method iterable<ProductCatalog>|ResultSetInterface<ProductCatalog>|false deleteMany(iterable $entities, array $options = [])
* @method iterable<ProductCatalog>|ResultSetInterface<ProductCatalog> deleteManyOrFail(iterable $entities, array $options = [])
*/
class ProductCatalogsTable 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_catalogs');
$this->setDisplayField('name');
$this->setPrimaryKey('id');
$this->hasMany('ProductCategories', [
'className' => 'CakeProducts.ProductCategories',
]);
$this->hasMany('ExternalProductCatalogs', [
'className' => 'CakeProducts.ExternalProductCatalogs',
]);
}
/**
* Default validation rules.
*
* @param Validator $validator Validator instance.
* @return Validator
*/
public function validationDefault(Validator $validator): Validator
{
$validator
->scalar('name')
->maxLength('name', 255)
->requirePresence('name', 'create')
->notEmptyString('name')
->add('name', 'unique', ['rule' => 'validateUnique', 'provider' => 'table']);
$validator
->scalar('catalog_description')
->maxLength('catalog_description', 255)
->allowEmptyString('catalog_description');
$validator
->boolean('enabled')
->requirePresence('enabled', 'create')
->notEmptyString('enabled');
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->isUnique(['name']), ['errorField' => 'name']);
return $rules;
}
}

View File

@@ -0,0 +1,164 @@
<?php
declare(strict_types=1);
namespace CakeProducts\Model\Table;
use Cake\Datasource\EntityInterface;
use Cake\Datasource\ResultSetInterface;
use Cake\ORM\Association\BelongsTo;
use Cake\ORM\Association\HasMany;
use Cake\ORM\Behavior\TreeBehavior;
use Cake\ORM\Query\SelectQuery;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use CakeProducts\Model\Entity\ProductCategory;
use Closure;
use Psr\SimpleCache\CacheInterface;
/**
* ProductCategories Model
*
* @property ProductCatalogsTable&BelongsTo $ProductCatalogs
* @property ProductCategoriesTable&BelongsTo $ParentProductCategories
* @property ProductCategoriesTable&HasMany $ChildProductCategories
*
* @method ProductCategory newEmptyEntity()
* @method ProductCategory newEntity(array $data, array $options = [])
* @method array<ProductCategory> newEntities(array $data, array $options = [])
* @method ProductCategory get(mixed $primaryKey, array|string $finder = 'all', CacheInterface|string|null $cache = null, Closure|string|null $cacheKey = null, mixed ...$args)
* @method ProductCategory findOrCreate($search, ?callable $callback = null, array $options = [])
* @method ProductCategory patchEntity(EntityInterface $entity, array $data, array $options = [])
* @method array<ProductCategory> patchEntities(iterable $entities, array $data, array $options = [])
* @method ProductCategory saveOrFail(EntityInterface $entity, array $options = [])
* @method iterable<ProductCategory>|ResultSetInterface<ProductCategory>|false saveMany(iterable $entities, array $options = [])
* @method iterable<ProductCategory>|ResultSetInterface<ProductCategory> saveManyOrFail(iterable $entities, array $options = [])
* @method iterable<ProductCategory>|ResultSetInterface<ProductCategory>|false deleteMany(iterable $entities, array $options = [])
* @method iterable<ProductCategory>|ResultSetInterface<ProductCategory> deleteManyOrFail(iterable $entities, array $options = [])
*
* @mixin TreeBehavior
*/
class ProductCategoriesTable extends Table
{
/**
* Current scope for Tree behavior - per catalog
*
* @var string
*/
protected $treeCatalogId;
/**
* Initialize method
*
* @param array<string, mixed> $config The configuration for the Table.
* @return void
*/
public function initialize(array $config): void
{
parent::initialize($config);
$this->treeCatalogId = 1;
$this->setTable('product_categories');
$this->setDisplayField('name');
$this->setPrimaryKey('id');
$this->addBehavior('Tree');
$this->belongsTo('ProductCatalogs', [
'foreignKey' => 'product_catalog_id',
'joinType' => 'INNER',
'className' => 'CakeProducts.ProductCatalogs',
]);
$this->belongsTo('ParentProductCategories', [
'className' => 'CakeProducts.ProductCategories',
'foreignKey' => 'parent_id',
]);
$this->hasMany('ChildProductCategories', [
'className' => 'CakeProducts.ProductCategories',
'foreignKey' => 'parent_id',
]);
$this->hasMany('ProductCategoryAttributes', [
'foreignKey' => 'product_category_id',
'bindingKey' => 'internal_id',
'className' => 'CakeProducts.ProductCategoryAttributes',
]);
$this->behaviors()->Tree->setConfig('scope', ['product_catalog_id' => $this->treeCatalogId]);
}
/**
* Default validation rules.
*
* @param Validator $validator Validator instance.
* @return Validator
*/
public function validationDefault(Validator $validator): Validator
{
$validator
->uuid('product_catalog_id')
->notEmptyString('product_catalog_id');
$validator
->scalar('name')
->maxLength('name', 255)
->requirePresence('name', 'create')
->notEmptyString('name');
$validator
->scalar('category_description')
->allowEmptyString('category_description');
$validator
->integer('parent_id')
->allowEmptyString('parent_id');
$validator
->boolean('enabled')
->notEmptyString('enabled');
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->isUnique(['product_catalog_id', 'name']), ['errorField' => 'product_catalog_id']);
$rules->add($rules->existsIn(['product_catalog_id'], 'ProductCatalogs'), ['errorField' => 'product_catalog_id']);
$rules->add($rules->existsIn(['parent_id'], 'ParentProductCategories'), ['errorField' => 'parent_id']);
return $rules;
}
/**
* @param int $catalogId
*
* @return void
*/
public function setConfigureCatalogId(string $catalogId)
{
$this->treeCatalogId = $catalogId;
$this->behaviors()->Tree->setConfig('scope', ['product_catalog_id' => $this->treeCatalogId]);
}
/**
* @param EntityInterface $entity
* @param array $options
*
* @return EntityInterface|false
*/
public function save(EntityInterface $entity, array $options = []): EntityInterface|false
{
$this->behaviors()->get('Tree')->setConfig([
'scope' => [
'product_catalog_id' => $entity->product_catalog_id,
],
]);
return parent::save($entity, $options);
}
}

View File

@@ -0,0 +1,103 @@
<?php
declare(strict_types=1);
namespace CakeProducts\Model\Table;
use Cake\Datasource\EntityInterface;
use Cake\Datasource\ResultSetInterface;
use Cake\ORM\Association\BelongsTo;
use Cake\ORM\Query\SelectQuery;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use CakeProducts\Model\Entity\ProductCategoryAttributeOption;
use Closure;
use Psr\SimpleCache\CacheInterface;
/**
* ProductCategoryAttributeOptions Model
*
* @property ProductCategoryAttributesTable&BelongsTo $ProductCategoryAttributes
*
* @method ProductCategoryAttributeOption newEmptyEntity()
* @method ProductCategoryAttributeOption newEntity(array $data, array $options = [])
* @method array<ProductCategoryAttributeOption> newEntities(array $data, array $options = [])
* @method ProductCategoryAttributeOption get(mixed $primaryKey, array|string $finder = 'all', CacheInterface|string|null $cache = null, Closure|string|null $cacheKey = null, mixed ...$args)
* @method ProductCategoryAttributeOption findOrCreate($search, ?callable $callback = null, array $options = [])
* @method ProductCategoryAttributeOption patchEntity(EntityInterface $entity, array $data, array $options = [])
* @method array<ProductCategoryAttributeOption> patchEntities(iterable $entities, array $data, array $options = [])
* @method ProductCategoryAttributeOption|false save(EntityInterface $entity, array $options = [])
* @method ProductCategoryAttributeOption saveOrFail(EntityInterface $entity, array $options = [])
* @method iterable<ProductCategoryAttributeOption>|ResultSetInterface<ProductCategoryAttributeOption>|false saveMany(iterable $entities, array $options = [])
* @method iterable<ProductCategoryAttributeOption>|ResultSetInterface<ProductCategoryAttributeOption> saveManyOrFail(iterable $entities, array $options = [])
* @method iterable<ProductCategoryAttributeOption>|ResultSetInterface<ProductCategoryAttributeOption>|false deleteMany(iterable $entities, array $options = [])
* @method iterable<ProductCategoryAttributeOption>|ResultSetInterface<ProductCategoryAttributeOption> deleteManyOrFail(iterable $entities, array $options = [])
*/
class ProductCategoryAttributeOptionsTable 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_attribute_options');
$this->setDisplayField('attribute_value');
$this->setPrimaryKey('id');
$this->belongsTo('ProductCategoryAttributes', [
'foreignKey' => 'product_category_attribute_id',
'joinType' => 'INNER',
'className' => 'CakeProducts.ProductCategoryAttributes',
]);
}
/**
* Default validation rules.
*
* @param Validator $validator Validator instance.
* @return Validator
*/
public function validationDefault(Validator $validator): Validator
{
$validator
->integer('product_category_attribute_id')
->notEmptyString('product_category_attribute_id');
$validator
->scalar('attribute_value')
->maxLength('attribute_value', 255)
->requirePresence('attribute_value', 'create')
->notEmptyString('attribute_value');
$validator
->scalar('attribute_label')
->maxLength('attribute_label', 255)
->requirePresence('attribute_label', 'create')
->notEmptyString('attribute_label');
$validator
->boolean('enabled')
->notEmptyString('enabled');
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_attribute_id'], 'ProductCategoryAttributes'), ['errorField' => '0']);
return $rules;
}
}

View File

@@ -0,0 +1,113 @@
<?php
declare(strict_types=1);
namespace CakeProducts\Model\Table;
use Cake\Database\Type\EnumType;
use Cake\Datasource\EntityInterface;
use Cake\Datasource\ResultSetInterface;
use Cake\ORM\Association\BelongsTo;
use Cake\ORM\Query\SelectQuery;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use CakeProducts\Model\Entity\ProductCategoryAttribute;
use CakeProducts\Model\Enum\ProductCategoryAttributeTypeId;
use Closure;
use Psr\SimpleCache\CacheInterface;
/**
* ProductCategoryAttributes Model
*
* @property ProductCategoriesTable&BelongsTo $ProductCategories
*
* @method ProductCategoryAttribute newEmptyEntity()
* @method ProductCategoryAttribute newEntity(array $data, array $options = [])
* @method array<ProductCategoryAttribute> newEntities(array $data, array $options = [])
* @method ProductCategoryAttribute get(mixed $primaryKey, array|string $finder = 'all', CacheInterface|string|null $cache = null, Closure|string|null $cacheKey = null, mixed ...$args)
* @method ProductCategoryAttribute findOrCreate($search, ?callable $callback = null, array $options = [])
* @method ProductCategoryAttribute patchEntity(EntityInterface $entity, array $data, array $options = [])
* @method array<ProductCategoryAttribute> patchEntities(iterable $entities, array $data, array $options = [])
* @method ProductCategoryAttribute|false save(EntityInterface $entity, array $options = [])
* @method ProductCategoryAttribute saveOrFail(EntityInterface $entity, array $options = [])
* @method iterable<ProductCategoryAttribute>|ResultSetInterface<ProductCategoryAttribute>|false saveMany(iterable $entities, array $options = [])
* @method iterable<ProductCategoryAttribute>|ResultSetInterface<ProductCategoryAttribute> saveManyOrFail(iterable $entities, array $options = [])
* @method iterable<ProductCategoryAttribute>|ResultSetInterface<ProductCategoryAttribute>|false deleteMany(iterable $entities, array $options = [])
* @method iterable<ProductCategoryAttribute>|ResultSetInterface<ProductCategoryAttribute> deleteManyOrFail(iterable $entities, array $options = [])
*/
class ProductCategoryAttributesTable 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_attributes');
$this->setDisplayField('name');
$this->setPrimaryKey('id');
$this->belongsTo('ProductCategories', [
'foreignKey' => 'product_category_id',
'bindingKey' => 'internal_id',
'className' => 'CakeProducts.ProductCategories',
]);
$this->hasMany('ProductCategoryAttributeOptions', [
'foreignKey' => 'product_category_attribute_id',
'className' => 'CakeProducts.ProductCategoryAttributeOptions',
'saveStrategy' => 'replace',
]);
$this->getSchema()->setColumnType('attribute_type_id', EnumType::from(ProductCategoryAttributeTypeId::class));
}
/**
* Default validation rules.
*
* @param Validator $validator Validator instance.
* @return Validator
*/
public function validationDefault(Validator $validator): Validator
{
$validator
->scalar('name')
->maxLength('name', 255)
->requirePresence('name', 'create')
->notEmptyString('name');
$validator
->uuid('product_category_id')
->allowEmptyString('product_category_id');
$validator
->integer('attribute_type_id')
->requirePresence('attribute_type_id', 'create')
->notEmptyString('attribute_type_id');
$validator
->boolean('enabled')
->requirePresence('enabled', 'create')
->notEmptyString('enabled');
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->isUnique(['name', 'product_category_id'], ['allowMultipleNulls' => true]), ['errorField' => 'name']);
$rules->add($rules->existsIn(['product_category_id'], 'ProductCategories'), ['errorField' => 'product_category_id']);
return $rules;
}
}

View File

@@ -0,0 +1,105 @@
<?php
declare(strict_types=1);
namespace CakeProducts\Model\Table;
use Cake\Database\Type\EnumType;
use Cake\Datasource\EntityInterface;
use Cake\Datasource\ResultSetInterface;
use Cake\ORM\Association\BelongsTo;
use Cake\ORM\Query\SelectQuery;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use CakeProducts\Model\Entity\Product;
use CakeProducts\Model\Enum\ProductProductTypeId;
use Closure;
use Psr\SimpleCache\CacheInterface;
/**
* Products Model
*
* @property ProductCategoriesTable&BelongsTo $ProductCategories
*
* @method Product newEmptyEntity()
* @method Product newEntity(array $data, array $options = [])
* @method array<Product> newEntities(array $data, array $options = [])
* @method Product get(mixed $primaryKey, array|string $finder = 'all', CacheInterface|string|null $cache = null, Closure|string|null $cacheKey = null, mixed ...$args)
* @method Product findOrCreate($search, ?callable $callback = null, array $options = [])
* @method Product patchEntity(EntityInterface $entity, array $data, array $options = [])
* @method array<Product> patchEntities(iterable $entities, array $data, array $options = [])
* @method Product|false save(EntityInterface $entity, array $options = [])
* @method Product saveOrFail(EntityInterface $entity, array $options = [])
* @method iterable<Product>|ResultSetInterface<Product>|false saveMany(iterable $entities, array $options = [])
* @method iterable<Product>|ResultSetInterface<Product> saveManyOrFail(iterable $entities, array $options = [])
* @method iterable<Product>|ResultSetInterface<Product>|false deleteMany(iterable $entities, array $options = [])
* @method iterable<Product>|ResultSetInterface<Product> deleteManyOrFail(iterable $entities, array $options = [])
*/
class ProductsTable 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('products');
$this->setDisplayField('name');
$this->setPrimaryKey('id');
$this->belongsTo('ProductCategories', [
'foreignKey' => 'product_category_id',
'bindingKey' => 'internal_id',
'joinType' => 'INNER',
'className' => 'CakeProducts.ProductCategories',
]);
$this->getSchema()->setColumnType('product_type_id', EnumType::from(ProductProductTypeId::class));
}
/**
* Default validation rules.
*
* @param Validator $validator Validator instance.
* @return Validator
*/
public function validationDefault(Validator $validator): Validator
{
$validator
->scalar('name')
->maxLength('name', 255)
->requirePresence('name', 'create')
->notEmptyString('name');
$validator
->uuid('product_category_id')
->notEmptyString('product_category_id');
$validator
->integer('product_type_id')
->requirePresence('product_type_id', 'create')
->notEmptyString('product_type_id');
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->isUnique(['product_category_id', 'name']), ['errorField' => '0']);
$rules->add($rules->existsIn(['product_category_id'], 'ProductCategories'), ['errorField' => '1']);
return $rules;
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace CakeProducts\Service;
use Cake\Core\ContainerInterface;
use Cake\Core\Plugin;
use Cake\Core\ServiceConfig;
use Cake\Core\ServiceProvider;
use Cake\Log\Log;
use Cake\ORM\Locator\LocatorAwareTrait;
use CakeProducts\Service\InternalCatalogManagerService;
class CatalogManagerServiceProvider extends ServiceProvider
{
protected array $provides = [
InternalCatalogManagerService::class
];
/**
* @param ContainerInterface $container
*
* @return void
*/
public function services(ContainerInterface $container): void
{
$container->add(InternalCatalogManagerService::class)
->addArgument(new ExternalCatalogManagerService());
}
}

View File

@@ -0,0 +1,184 @@
<?php
namespace CakeProducts\Service;
use Cake\Cache\Cache;
use Cake\Core\ServiceConfig;
use Cake\Http\Client;
use Cake\I18n\FrozenTime;
use Cake\I18n\Time;
use Cake\Log\Log;
use Cake\ORM\Locator\LocatorAwareTrait;
use CakeProducts\Model\Entity\ExternalProductCatalog;
use CakeProducts\Model\Entity\ProductCategory;
use CakeProducts\Model\Table\ProductCatalogsTable;
class ExternalCatalogManagerService
{
use LocatorAwareTrait;
/**
* @var ProductCatalogsTable
*/
protected \Cake\ORM\Table|ProductCatalogsTable $ProductCatalogs;
/**
* @var ServiceConfig
*/
protected ServiceConfig $serviceConfig;
/**
* @var Client
*/
protected Client $httpClient;
/**
*
*/
public function __construct()
{
$this->ProductCatalogs = $this->fetchTable('CakeProducts.ProductCatalogs');
$this->serviceConfig = new ServiceConfig();
$this->httpClient = new Client([
// 'host' => $config['base_url'],
// 'scheme' => 'https',
// 'scheme' => 'http',
]);
}
public function newCategoryCreated(ProductCategory $productCategory)
{
$results = [];
$externalProductCatalogs = $this->_getExternalProductCatalogsForCatalogId($productCategory->product_catalog_id);
foreach ($externalProductCatalogs as $externalProductCatalog) {
$results[] = $this->_createNewCategoryForExternalProductCatalog($externalProductCatalog, $productCategory);
}
return $results;
}
protected function _createNewCategoryForExternalProductCatalog(ExternalProductCatalog $externalProductCatalog, ProductCategory $productCategory)
{
$url = $externalProductCatalog->api_url . '/product-categories';
$response = $this->postToUrl($url, $productCategory->toArray());
Log::debug(print_r('$response->getJson()', true));
Log::debug(print_r($response->getJson(), true));
Log::debug(print_r('$response->getStatusCode()', true));
Log::debug(print_r($response->getStatusCode(), true));
return $response->getStatusCode();
}
/**
* @return mixed|null
*/
public function getJwtToken()
{
Log::debug('inside getJwtToken');
if (Cache::read('product_catalog_api_token')) {
Log::debug('token was cached');
// return Cache::read('product_catalog_api_token');
} else {
Log::debug('token was **NOT** cached');
}
$response = $this->httpClient->post('http://localhost:8766/api/v1/users/token', json_encode([
'username' => 'test',
'password' => 'test',
]), ['headers' => ['Accept' => 'application/json', 'Content-Type' => 'application/json']]);
// $this->httpClient->getConfig();
if ($response->isOk()) {
$json = $response->getJson();
$token = array_key_exists('token', $json) ? $json['token'] : null;
Cache::write('product_catalog_api_token', $token);
Log::debug('$token');
Log::debug($token);
return $token;
}
Log::debug('$response->getStringBody()');
Log::debug($response->getStringBody());
Log::debug(print_r('$response->getStatusCode()', true));
Log::debug(print_r($response->getStatusCode(), true));
return null;
}
public function postToUrl(string $url, array $data, int $tries = 0)
{
// if (true || !Cache::read('product_catalog_api_token')) {
$token = $this->getJwtToken();
// }
Log::debug('$token inside postToUrl' . $token);
Log::debug('Cache::read(product_catalog_api_token)');
Log::debug(Cache::read('product_catalog_api_token') ? Cache::read('product_catalog_api_token') : 'NULL');
Log::debug('ATTEMPT # ' . $tries);
$response = $this->httpClient->post($url, json_encode($data), [
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
// 'Authorization' => 'Bearer ' . base64_encode(Cache::read('product_catalog_api_token'))
'Authorization' => 'Bearer ' . $token,
]
]);
if (!$response->isOk()) {
$tries++;
}
if ($tries > 3) {
return $response;
}
if ($response->getStatusCode() == 401) {
$this->postToUrl($url, $data, $tries);
}
Log::debug('$response->getJson');
Log::debug(print_r($response->getJson(), true));
return $response;
}
/**
* @param string $url
* @param array $data
* @param int $tries
*
* @return mixed
*/
public function putToUrl(string $url, array $data, int $tries = 0)
{
if (!Cache::read('product_catalog_api_token')) {
$this->getJwtToken();
}
$response = $this->httpClient->put($url, json_encode($data), [
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . Cache::read('product_catalog_api_token')
],
]);
if (!$response->isOk()) {
$tries++;
}
if ($tries > 3) {
return $response;
}
if ($response->getStatusCode() == 401) {
$this->getJwtToken();
$this->putToUrl($url, $data, $tries);
}
return $response;
}
protected function _getExternalProductCatalogsForCatalogId(string $productCatalogId)
{
return $this->ProductCatalogs->ExternalProductCatalogs->find()->where(['product_catalog_id' => $productCatalogId])->toArray();
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace CakeProducts\Service;
use Cake\Core\ServiceConfig;
use Cake\Datasource\EntityInterface;
use Cake\I18n\FrozenTime;
use Cake\I18n\Time;
use Cake\Log\Log;
use Cake\ORM\Locator\LocatorAwareTrait;
use Cake\ORM\Table;
use Cake\Utility\Text;
use CakeProducts\Model\Entity\ProductCategory;
use CakeProducts\Model\Table\ProductCatalogsTable;
class InternalCatalogManagerService
{
use LocatorAwareTrait;
/**
* @var ProductCatalogsTable
*/
protected Table|ProductCatalogsTable $ProductCatalogs;
/**
* @var ServiceConfig
*/
protected ServiceConfig $serviceConfig;
/**
* @var ExternalCatalogManagerService|null
*/
protected ?ExternalCatalogManagerService $externalCatalogManager;
/**
*
*/
public function __construct(ExternalCatalogManagerService $externalCatalogManagerService)
{
$this->ProductCatalogs = $this->fetchTable('CakeProducts.ProductCatalogs');
$this->serviceConfig = new ServiceConfig();
if ($this->serviceConfig->get('CakeProducts.internal.enabled') && $this->serviceConfig->get('CakeProducts.internal.syncExternally')) {
$this->externalCatalogManager = $externalCatalogManagerService;
}
}
public function getCatalog(string $id = null)
{
$contain = ['ProductCategories'];
if ($this->serviceConfig->get('CakeProducts.internal.syncExternally')) {
$contain[] = 'ExternalProductCatalogs';
}
return $this->ProductCatalogs->get($id, contain: $contain);
}
/**
* @param string|null $id
*
* @return \App\Model\Entity\ProductCategory|EntityInterface
*/
public function getCategory(string $id = null)
{
$contain = ['ProductCatalogs', 'ParentProductCategories', 'ChildProductCategories'];
return $this->ProductCatalogs->ProductCategories->get($id, contain: $contain);
}
/**
* @param ProductCategory $productCategory product category entity
* @param array $data data to save
*
* @return array
*/
public function createNewCategory(ProductCategory $productCategory, array $data = []): array
{
$now = Time::now();
$associated = [];
Log::info('posted data - adding new ProductCategory');
Log::info(print_r($data, true));
$saveOptions = [
'associated' => $associated,
];
if (!array_key_exists('internal_id', $data) || !$data['internal_id']) {
$data['internal_id'] = Text::uuid();
}
$productCategory = $this->ProductCatalogs->ProductCategories->patchEntity($productCategory, $data, $saveOptions);
if ($productCategory->getErrors()) {
Log::debug(print_r('$productCategory->getErrors() next - failed to save from create new product category', true));
Log::debug(print_r($productCategory->getErrors(), true));
}
$returnData = [
'entity' => $productCategory,
'result' => $this->ProductCatalogs->ProductCategories->save($productCategory, $saveOptions),
'apiResults' => [],
];
if ($returnData['result'] && $this->externalCatalogManager) {
$returnData['apiResults'] = $this->externalCatalogManager->newCategoryCreated($returnData['result']);
}
return $returnData;
}
}