Add product options and department categories API
Introduce endpoints for managing product options and department categories, including listing, creating, editing, and deleting functionalities. Updated related objects and traits to support new operations, including added array serialization methods and improved query handling for better flexibility.
This commit is contained in:
@@ -80,4 +80,16 @@ class categories_o extends db
|
||||
self::objectChanged();
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function asArray(): array
|
||||
{
|
||||
return [
|
||||
'id' => (int)$this->id,
|
||||
'name' => (string)$this->name,
|
||||
'description' => (string)$this->description,
|
||||
'meta' => [
|
||||
'product_count' => (int)(new products_o())->countRowsWhere(['category' => $this->id])
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace objects;
|
||||
|
||||
use classes\db;
|
||||
use classes\object_property;
|
||||
use Exception;
|
||||
use traits\db_object_t;
|
||||
|
||||
class department_categories_o extends db
|
||||
{
|
||||
use db_object_t;
|
||||
|
||||
public object_property $department_id;
|
||||
public object_property $category_id;
|
||||
public object_property $created_at;
|
||||
public object_property $updated_at;
|
||||
public object_property $deleted_at;
|
||||
|
||||
|
||||
public function structure(): void
|
||||
{
|
||||
$this->setTable('department_categories');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a department category, and set this object to the new object
|
||||
* @param int $department_id
|
||||
* @param int $category_id
|
||||
* @return void
|
||||
* @throws Exception If the object was not created successfully
|
||||
*/
|
||||
public function add(int $department_id, int $category_id): void
|
||||
{
|
||||
// Make sure we don't add the same department category twice
|
||||
if (count(self::getFieldsWhere(
|
||||
[
|
||||
'department_id' => $department_id,
|
||||
'category_id' => $category_id,
|
||||
'deleted_at' => null
|
||||
], ['id'])) > 0) {
|
||||
throw new Exception('Department category already exists');
|
||||
}
|
||||
$tmp_id = self::add_object([
|
||||
'department_id' => $department_id,
|
||||
'category_id' => $category_id
|
||||
]);
|
||||
$this->id = $tmp_id;
|
||||
self::getObjectProperties();
|
||||
self::objectChanged();
|
||||
}
|
||||
|
||||
public function getObjectProperties(): void
|
||||
{
|
||||
$this->department_id = new object_property($this->table, $this->id, 'department_id', 'int', false);
|
||||
$this->category_id = new object_property($this->table, $this->id, 'category_id', 'int', false);
|
||||
$this->created_at = new object_property($this->table, $this->id, 'created_at', 'string', false);
|
||||
$this->updated_at = new object_property($this->table, $this->id, 'updated_at', 'string', false);
|
||||
$this->deleted_at = new object_property($this->table, $this->id, 'deleted_at', 'string', false);
|
||||
}
|
||||
|
||||
public function objectChanged(): void
|
||||
{
|
||||
//TODO: Add cache invalidation
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the categories for a department
|
||||
* @param int $department_id
|
||||
* @return array The categories for the department
|
||||
*/
|
||||
public function getCategoriesForDepartment(int $department_id, $parseFunction = null): array
|
||||
{
|
||||
$category_ids = self::getFieldsWhere(
|
||||
[
|
||||
'department_id' => $department_id,
|
||||
'deleted_at' => null
|
||||
],
|
||||
['id', 'category_id']
|
||||
);
|
||||
$categories = [];
|
||||
foreach ( $category_ids as $category_id ) {
|
||||
$tmp = (new department_categories_o())->select($category_id['id'])->asArray();
|
||||
$tmp['category'] = (new categories_o())->select($tmp['category_id']);
|
||||
$categories[] = $tmp;
|
||||
|
||||
}
|
||||
if ($parseFunction) {
|
||||
$tmp = [];
|
||||
foreach ( $categories as $category ) {
|
||||
$tmp[] = $parseFunction($category);
|
||||
}
|
||||
$categories = $tmp;
|
||||
}
|
||||
return $categories;
|
||||
|
||||
}
|
||||
|
||||
public function asArray(): array
|
||||
{
|
||||
return [
|
||||
'id' => (int)$this->id,
|
||||
'department_id' => (int)$this->department_id->value(),
|
||||
'category_id' => (int)$this->category_id->value(),
|
||||
'created_at' => (string)$this->created_at->value(),
|
||||
'updated_at' => (string)$this->updated_at->value(),
|
||||
'deleted_at' => (string)$this->deleted_at->value()
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace objects;
|
||||
|
||||
use classes\db;
|
||||
use classes\object_property;
|
||||
use Exception;
|
||||
use traits\db_object_t;
|
||||
|
||||
class product_options_o extends db
|
||||
{
|
||||
use db_object_t;
|
||||
|
||||
public object_property $product_id;
|
||||
public object_property $option_id;
|
||||
public object_property $name;
|
||||
|
||||
public function structure(): void
|
||||
{
|
||||
$this->setTable('products_options');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a product option, and set this object to the new object
|
||||
* @param int $product_id
|
||||
* @param int $option_id
|
||||
* @return void
|
||||
* @throws Exception If the object was not created successfully
|
||||
*/
|
||||
public function add(int $product_id, int $option_id): void
|
||||
{
|
||||
$tmp_id = self::add_object([
|
||||
'product_id' => $product_id,
|
||||
'option_id' => $option_id
|
||||
]);
|
||||
$this->id = $tmp_id;
|
||||
self::getObjectProperties();
|
||||
self::objectChanged();
|
||||
}
|
||||
|
||||
public function getObjectProperties(): void
|
||||
{
|
||||
$this->product_id = new object_property($this->table, $this->id, 'product_id', 'int', false);
|
||||
$this->option_id = new object_property($this->table, $this->id, 'option_id', 'int', false);
|
||||
$this->name = new object_property($this->table, $this->id, 'name', 'string', false);
|
||||
}
|
||||
|
||||
public function objectChanged(): void
|
||||
{
|
||||
//TODO: Add cache invalidation
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the product option
|
||||
* @param string $name
|
||||
* @throws Exception If the object was not selected
|
||||
*/
|
||||
public function set_name(string $name): void
|
||||
{
|
||||
self::requireSelected();
|
||||
$this->name->set($name);
|
||||
self::objectChanged();
|
||||
}
|
||||
|
||||
public function getProductOptions(int $id): array
|
||||
{
|
||||
$options = self::getFieldsWhere(['product_id' => $id],
|
||||
[
|
||||
'id',
|
||||
'product_id',
|
||||
'option_id',
|
||||
'name',
|
||||
'created_at',
|
||||
'updated_at'
|
||||
]
|
||||
);
|
||||
$tmp = [];
|
||||
foreach ( $options as $option ) {
|
||||
$tmp_product = (new products_o())->select($option['option_id'])->asArray();
|
||||
$tmp[] = [
|
||||
'id' => (int)$option['id'],
|
||||
'product_id' => (int)$option['product_id'],
|
||||
'option_id' => (int)$option['option_id'],
|
||||
'name' => (string)$option['name'] === '' ? $tmp_product['name'] : (string)$option['name'],
|
||||
'created_at' => (string)$option['created_at'],
|
||||
'updated_at' => (string)$option['updated_at'],
|
||||
'product' => $tmp_product
|
||||
];
|
||||
}
|
||||
return $tmp;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace routes;
|
||||
|
||||
use classes\authentication;
|
||||
use objects\categories_o;
|
||||
use objects\department_categories_o;
|
||||
use objects\departments_o;
|
||||
use objects\logs_o;
|
||||
use traits\route_t;
|
||||
@@ -128,5 +130,135 @@ class departmentsRoute
|
||||
$response->error('Invalid session', 400);
|
||||
}
|
||||
});
|
||||
|
||||
$this->get('/departments/categories', function () {
|
||||
// Require the user to be logged in
|
||||
global $response;
|
||||
self::requirePermission('list_department_categories');
|
||||
// Get the user object
|
||||
$user = (new authentication())->get_user();
|
||||
// Check if the request was successful
|
||||
if ($user) {
|
||||
// Require the department id
|
||||
self::requireParameters(['id']);
|
||||
self::requireType((int)self::getParameter('id'), self::TYPE_INT());
|
||||
// Get the department object
|
||||
$department = (new departments_o())->select(self::getParameter('id'));
|
||||
// Validate the department categories object
|
||||
if (!$department->exists()) {
|
||||
// Log the incident
|
||||
(new logs_o())->add('departments', 'global', 1, $user->id, 'LIST_DEPARTMENT_CATEGORIES', 'Department categories not found');
|
||||
// Return an error
|
||||
$response->error('Department categories not found', 400);
|
||||
}
|
||||
// Get the department categories
|
||||
$department_categories = new department_categories_o();
|
||||
// Log the incident
|
||||
(new logs_o())->add('departments', 'global', 1, $user->id, 'LIST_DEPARTMENT_CATEGORIES', 'Successfully listed department categories');
|
||||
// Return the list of department categories
|
||||
$response->success(
|
||||
$department_categories
|
||||
->getCategoriesForDepartment(self::getParameter('id'),
|
||||
function ($department_category) {
|
||||
return [
|
||||
'id' => (int)$department_category['id'],
|
||||
'department_id' => (int)$department_category['department_id'],
|
||||
'category_id' => (int)$department_category['category_id'],
|
||||
'created_at' => (string)$department_category['created_at'],
|
||||
'updated_at' => $department_category['updated_at'],
|
||||
'category' => $department_category['category']->asArray()
|
||||
];
|
||||
}
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// Log the incident
|
||||
(new logs_o())->add('departments', 'global', 1, 0, 'LIST_DEPARTMENT_CATEGORIES', 'No user found, or invalid session');
|
||||
// Return an error
|
||||
$response->error('Invalid session', 400);
|
||||
}
|
||||
});
|
||||
|
||||
$this->post('/departments/categories', function () {
|
||||
// Require the user to be logged in
|
||||
global $response;
|
||||
self::requirePermission('add_department_category');
|
||||
// Get the user object
|
||||
$user = (new authentication())->get_user();
|
||||
// Check if the request was successful
|
||||
if ($user) {
|
||||
// Require the parameters
|
||||
self::requireParameters(['department_id', 'category_id']);
|
||||
self::requireType((int)self::getParameter('department_id'), self::TYPE_INT());
|
||||
self::requireType((int)self::getParameter('category_id'), self::TYPE_INT());
|
||||
// Get the department object
|
||||
$department = (new departments_o())->select(self::getParameter('department_id'));
|
||||
// Validate the department object
|
||||
if (!$department->exists()) {
|
||||
// Log the incident
|
||||
(new logs_o())->add('departments', 'global', 1, $user->id, 'ADD_DEPARTMENT_CATEGORY', 'Department not found');
|
||||
// Return an error
|
||||
$response->error('Department not found', 400);
|
||||
}
|
||||
// Get the category object
|
||||
$category = (new categories_o())->select(self::getParameter('category_id'));
|
||||
// Validate the category object
|
||||
if (!$category->exists()) {
|
||||
// Log the incident
|
||||
(new logs_o())->add('departments', 'global', 1, $user->id, 'ADD_DEPARTMENT_CATEGORY', 'Category not found');
|
||||
// Return an error
|
||||
$response->error('Category not found', 400);
|
||||
}
|
||||
// Add the department category
|
||||
$department_categories = new department_categories_o();
|
||||
$department_categories->add(
|
||||
self::getParameter('department_id'),
|
||||
self::getParameter('category_id')
|
||||
);
|
||||
// Log the incident
|
||||
(new logs_o())->add('departments', 'global', 1, $user->id, 'ADD_DEPARTMENT_CATEGORY', 'Successfully added a department category');
|
||||
// Return a success message
|
||||
$response->success(['message' => 'Department category added successfully']);
|
||||
} else {
|
||||
// Log the incident
|
||||
(new logs_o())->add('departments', 'global', 1, 0, 'ADD_DEPARTMENT_CATEGORY', 'No user found, or invalid session');
|
||||
// Return an error
|
||||
$response->error('Invalid session', 400);
|
||||
}
|
||||
});
|
||||
|
||||
$this->delete('/departments/categories', function () {
|
||||
// Require the user to be logged in
|
||||
global $response;
|
||||
self::requirePermission('delete_department_category');
|
||||
// Get the user object
|
||||
$user = (new authentication())->get_user();
|
||||
// Check if the request was successful
|
||||
if ($user) {
|
||||
// Require the parameters
|
||||
self::requireParameters(['id']);
|
||||
self::requireType((int)self::getParameter('id'), self::TYPE_INT());
|
||||
// Get the department category object
|
||||
$department_category = (new department_categories_o())->select(self::getParameter('id'));
|
||||
// Validate the department category object
|
||||
if (!$department_category->exists()) {
|
||||
// Log the incident
|
||||
(new logs_o())->add('departments', 'global', 1, $user->id, 'DELETE_DEPARTMENT_CATEGORY', 'Department category not found');
|
||||
// Return an error
|
||||
$response->error('Department category not found', 400);
|
||||
}
|
||||
// Delete the department category
|
||||
$department_category->delete();
|
||||
// Log the incident
|
||||
(new logs_o())->add('departments', 'global', 1, $user->id, 'DELETE_DEPARTMENT_CATEGORY', 'Successfully deleted a department category');
|
||||
// Return a success message
|
||||
$response->success(['message' => 'Department category deleted successfully']);
|
||||
} else {
|
||||
// Log the incident
|
||||
(new logs_o())->add('departments', 'global', 1, 0, 'DELETE_DEPARTMENT_CATEGORY', 'No user found, or invalid session');
|
||||
// Return an error
|
||||
$response->error('Invalid session', 400);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
namespace routes;
|
||||
|
||||
use classes\authentication;
|
||||
use objects\logs_o;
|
||||
use objects\product_options_o;
|
||||
use traits\route_t;
|
||||
|
||||
class productOptionsRoute
|
||||
{
|
||||
use route_t;
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$this->get('/product/options', function () {
|
||||
|
||||
// Require the user to be logged in
|
||||
global $response;
|
||||
$this->requirePermission('list_product_options');
|
||||
// Get the user object
|
||||
$user = (new authentication())->get_user();
|
||||
// Check if the request was successful
|
||||
if ($user) {
|
||||
// Log the incident
|
||||
(new logs_o())->add('product_options', 'global', 1, $user->id, 'LIST_PRODUCT_OPTIONS', 'User listed product options');
|
||||
// Return the list of departments
|
||||
$response->success(
|
||||
(new product_options_o())
|
||||
->setSearchableFields([
|
||||
// The fields that can be searched. This would otherwise make it possible to get secret information from the database, simply by searching for it and getting the result count back
|
||||
'id',
|
||||
'product_id',
|
||||
'option_id',
|
||||
])
|
||||
->listObjectsWithPaginationIfSet(
|
||||
function ($option) use ($user) {
|
||||
// Return the object as an array
|
||||
return [
|
||||
'id' => (int)$option['id'],
|
||||
'product_id' => (int)$option['product_id'],
|
||||
'option_id' => (int)$option['option_id'],
|
||||
'created_at' => (string)$option['created_at'],
|
||||
'updated_at' => (string)$option['updated_at'],
|
||||
];
|
||||
}
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// Log the incident
|
||||
(new logs_o())->add('product_options', 'global', 1, 0, 'LIST_PRODUCT_OPTIONS', 'User tried to list product options without being logged in');
|
||||
// Return an error
|
||||
$response->error('Invalid session', 400);
|
||||
}
|
||||
});
|
||||
|
||||
$this->post('/product/options', function () {
|
||||
// Require the user to be logged in
|
||||
global $response;
|
||||
$this->requirePermission('add_product_option');
|
||||
// Get the user object
|
||||
$user = (new authentication())->get_user();
|
||||
// Check if the request was successful
|
||||
if ($user) {
|
||||
// Check if the required parameters are set
|
||||
self::requireParameters(['product_id', 'option_id']);
|
||||
// Check if the parameters are of the correct type
|
||||
self::requireType(self::getParameter('product_id'), self::TYPE_INT());
|
||||
self::requireType(self::getParameter('option_id'), self::TYPE_INT());
|
||||
// Create the object
|
||||
$product_options = new product_options_o();
|
||||
// Add the object
|
||||
$product_options->add(
|
||||
self::getParameter('product_id'),
|
||||
self::getParameter('option_id')
|
||||
);
|
||||
// Log the incident
|
||||
(new logs_o())->add('product_options', 'global', 1, $user->id, 'ADD_PRODUCT_OPTION', 'User added a product option');
|
||||
// Return the object
|
||||
$response->success(
|
||||
$product_options->__toString()
|
||||
);
|
||||
} else {
|
||||
// Log the incident
|
||||
(new logs_o())->add('product_options', 'global', 1, 0, 'ADD_PRODUCT_OPTION', 'User tried to add a product option without being logged in');
|
||||
// Return an error
|
||||
$response->error('Invalid session', 400);
|
||||
}
|
||||
});
|
||||
|
||||
$this->put('/product/options', function () {
|
||||
// Require the user to be logged in
|
||||
global $response;
|
||||
$this->requirePermission('edit_product_option');
|
||||
// Get the user object
|
||||
$user = (new authentication())->get_user();
|
||||
// Check if the request was successful
|
||||
if ($user) {
|
||||
// Check if the required parameters are set
|
||||
self::requireParameters(['id', 'name']);
|
||||
// Check if the parameters are of the correct type
|
||||
self::requireType(self::getParameter('id'), self::TYPE_INT());
|
||||
// Create the object
|
||||
$product_options = new product_options_o();
|
||||
// Select the object
|
||||
$product_options->select(self::getParameter('id'));
|
||||
// Set the name
|
||||
$product_options->set_name(self::getParameter('name'));
|
||||
// Log the incident
|
||||
(new logs_o())->add('product_options', 'global', 1, $user->id, 'EDIT_PRODUCT_OPTION', 'User edited a product option');
|
||||
// Return the object
|
||||
$response->success(
|
||||
$product_options->__toString()
|
||||
);
|
||||
} else {
|
||||
// Log the incident
|
||||
(new logs_o())->add('product_options', 'global', 1, 0, 'EDIT_PRODUCT_OPTION', 'User tried to edit a product option without being logged in');
|
||||
// Return an error
|
||||
$response->error('Invalid session', 400);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace routes;
|
||||
|
||||
use classes\authentication;
|
||||
use objects\logs_o;
|
||||
use objects\product_options_o;
|
||||
use objects\products_o;
|
||||
use traits\route_t;
|
||||
|
||||
@@ -26,7 +27,8 @@ class productsRoute
|
||||
'economic_product_id' => (int)$product['economic_product_id'],
|
||||
'apply_category_discount' => (int)$product['apply_category_discount'],
|
||||
'created_at' => (string)$product['created_at'],
|
||||
'updated_at' => (string)$product['updated_at']
|
||||
'updated_at' => (string)$product['updated_at'],
|
||||
'addons' => (new product_options_o())->getProductOptions($product['id'])
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,12 @@ use classes\db;
|
||||
use classes\response;
|
||||
use Exception;
|
||||
use objects\bookings_o;
|
||||
use objects\categories_o;
|
||||
use objects\cron_o;
|
||||
use objects\customer_codes_o;
|
||||
use objects\customer_notes_o;
|
||||
use objects\customer_vehicles_o;
|
||||
use objects\department_categories_o;
|
||||
use objects\departments_o;
|
||||
use objects\economic_module_orders;
|
||||
use objects\logs_o;
|
||||
@@ -17,6 +19,7 @@ use objects\order_items_o;
|
||||
use objects\orders_o;
|
||||
use objects\plate_scanners_o;
|
||||
use objects\plate_scans_o;
|
||||
use objects\product_options_o;
|
||||
use objects\products_o;
|
||||
use objects\ratelimit_o;
|
||||
use objects\tokens_o;
|
||||
@@ -29,6 +32,7 @@ trait db_object_t
|
||||
public int $id; // The id of the object in the database
|
||||
private string $table; // The table of the objects in the database (e.g. users)
|
||||
private array $searchableFields = []; // The fields to search in the database (e.g. ['name', 'email']). If empty, all fields will be searched
|
||||
private array $whereClauses = []; // The where clauses to add to the pagination query
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -64,7 +68,12 @@ trait db_object_t
|
||||
$table = $this->table;
|
||||
$where = [];
|
||||
foreach ( $fieldsAndValues as $field => $value ) {
|
||||
$where[] = "$field = '$value'";
|
||||
// If the value is null, add a where clause to check if the field is null
|
||||
if ($value === null) {
|
||||
$where[] = "$field IS NULL";
|
||||
} else {
|
||||
$where[] = "$field = '$value'";
|
||||
}
|
||||
}
|
||||
$where = implode(' AND ', $where);
|
||||
$sql = "SELECT " . implode(', ', $fields) . " FROM $table WHERE $where";
|
||||
@@ -83,6 +92,18 @@ trait db_object_t
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where clause to the pagination query
|
||||
* @param string $field
|
||||
* @param mixed $value
|
||||
* @return users_o|bookings_o|categories_o|cron_o|customer_codes_o|customer_notes_o|customer_vehicles_o|department_categories_o|departments_o|economic_module_orders|logs_o|order_items_o|orders_o|plate_scanners_o|plate_scans_o|product_options_o|products_o|ratelimit_o|tokens_o|user_key_value_pairs_o|user_price_overrides_o|db_object_t
|
||||
*/
|
||||
public function addWhereClause(string $field, mixed $value): self
|
||||
{
|
||||
$this->whereClauses[] = "$field = '$value'";
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
// Return the object as a string
|
||||
@@ -111,6 +132,7 @@ trait db_object_t
|
||||
/**
|
||||
* List objects with pagination (if set)
|
||||
* @return array The list of objects in the table
|
||||
* @throws Exception
|
||||
*/
|
||||
public function listObjectsWithPaginationIfSet($parseFunction = null): array
|
||||
{
|
||||
@@ -177,6 +199,11 @@ trait db_object_t
|
||||
$whereClauses = [];
|
||||
$params = [];
|
||||
|
||||
// Add where clauses from the object, if set. This is done to allow for custom where clauses in routes, while still allowing for pagination
|
||||
if (!empty($this->whereClauses)) {
|
||||
$whereClauses = $this->whereClauses;
|
||||
}
|
||||
|
||||
// Search clause
|
||||
if (!empty($search)) {
|
||||
// Use prepared statements to prevent SQL injection
|
||||
@@ -593,7 +620,8 @@ trait db_object_t
|
||||
$id = $this->id;
|
||||
$sql = "SELECT * FROM $this->table WHERE id = $id";
|
||||
$result = $db->query($sql);
|
||||
return $result->num_rows > 0;
|
||||
// Check if the object exists, and if there's a deleted_at column, check if the object is not deleted
|
||||
return $db->num_rows($result) > 0 && (!$this->columnsExist(['deleted_at']) || $db->fetch_assoc($result)['deleted_at'] === null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user