Add custom routes, subscription features, and refactor orders

Introduced a new POST route for collected vehicle subscription invoices and enhanced responses with wash subscription transactions. Refactored order handling by adding department-based pricing logic and simplifying reusable methods. Various minor improvements include exception handling, input validation, and updated permissions.
This commit is contained in:
Jepp9350
2025-04-28 14:24:11 +02:00
parent c19ed3389c
commit 6cc5e6bbf0
7 changed files with 299 additions and 132 deletions
@@ -15,7 +15,6 @@ class customer_vehicles_addons_o extends db
public object_property $addon_id; // The id of the addon
public object_property $amount; // The amount of the addon
public function structure(): void
{
$this->setTable('customer_vehicles_addons');
@@ -53,15 +52,43 @@ class customer_vehicles_addons_o extends db
//TODO: Add cache invalidation
}
/**
* Convert the object to an array
* @throws Exception If the object is not selected
* @throws Exception If the object is not foun
*/
public function asArray(): array
{
self::requireSelected();
return [
'id' => (int)$this->id,
'vehicle_id' => (int)$this->vehicle_id->value(),
'addon_id' => (int)$this->addon_id->value(),
'amount' => (int)$this->amount->value(),
'product' => self::getProduct($this->getOption((int)$this->addon_id->value()))->asArray(),
];
}
/**
* @param product_options_o $product_option
* @return products_o
* @throws Exception If the object is not selected
* @throws Exception If the product is not selected
* @throws Exception If the product is not found
*/
private function getProduct(product_options_o $product_option): products_o
{
return (new products_o())->select((int)$product_option->option_id->value());
}
/**
* @return product_options_o
* @throws Exception If the object is not selected
*/
private function getOption(): product_options_o
{
self::requireSelected();
return (new product_options_o())->select((int)$this->addon_id->value());
}
}
@@ -46,6 +46,7 @@ class customer_vehicles_o extends db
);
return [
'id' => (int)$this->id,
'user_id' => (int)(new users_o())->getUserIdFromEconomic((int)$this->customer_id->value()),
'customer_id' => (int)$this->customer_id->value(),
'type' => (int)$this->type->value(),
'reg' => (string)$this->reg->value(),
@@ -53,6 +54,7 @@ class customer_vehicles_o extends db
'addons' => [
'enabled' => count($addons),
'available' => count($available_addons),
'list' => $addons,
]
];
}
+147 -124
View File
@@ -73,130 +73,6 @@ class orders_o extends db
$this->booking_id = new object_property($this->table, $this->id, 'booking_id', 'int', false);
}
public function includeIncludes(): orders_o
{
global /** @var response $response */
$response;
$includeEverything = $response->getRequestParameter('include_all') === 'true';
/** orderItems */
if ($response->getRequestParameter('includeOrderItems') || $includeEverything) {
$response->add_include('orderItems', $this->applyDepartmentPrices($this->getOrderItems($this->id), $this->department_id->value()));
}
/** customer */
if ($response->getRequestParameter('includeCustomer') || $includeEverything) {
$customer = new users_o();
$response->add_include('customer', $customer->getOrImportCustomerByCustomerNumber($this->customer_id->value())->includeIncludes()->asArray());
}
/** cashier */
if ($response->getRequestParameter('includeCashier') || $includeEverything) {
$cashier = new users_o();
$response->add_include('cashier', $cashier->getUserById($this->cashier_id->value())->asArray());
}
/**
* economicModuleOrders
*/
if ($response->getRequestParameter('includeEconomicModuleOrders') || $includeEverything) {
$response->add_include('economicModuleOrders', $this->economic_module_orders->getByOrderId($this->id)->asArray());
}
/**
* stripeModuleOrders
*/
if ($response->getRequestParameter('includeStripeModuleOrders') || $includeEverything) {
$response->add_include('stripeModuleOrders', $this->stripe_module_orders->exists() ? $this->stripe_module_orders->asArray() : []);
}
return $this;
}
/**
* Apply department pricing to a list of products
* @param array $order_items
* @param int $department_id
* @return array
*/
public function applyDepartmentPrices(array $order_items, int $department_id): array
{
global $response;
$department = new departments_o();
$department->getDepartmentById($department_id);
$department->getDepartmentProductPrices($department_id);
foreach ( $order_items as $key => $order_item ) {
$product = new products_o();
$product->getProductById($order_item['product_id']);
$order_items[$key]['product']['price'] = $product->getDepartmentPrice($department_id);
}
return $order_items;
}
public function getOrderItems(int $order_id): array
{
global $db;
$sql = "SELECT * FROM order_items WHERE order_id = $order_id";
$result = $db->query($sql);
$order_items = [];
if ($result->num_rows > 0 && $result) {
while ($row = $result->fetch_assoc()) {
$order_item = new order_items_o();
$order_items[] = $order_item->getOrderItemById($row['id'])->getItemAsArray();
}
}
return $order_items;
}
public function asArray(): array
{
return [
'id' => $this->id,
'customer_id' => (int)$this->customer_id->value(),
'cashier_id' => (int)$this->cashier_id->value(),
'reference' => $this->reference->value(),
'notes' => $this->notes->value(),
'department_id' => (int)$this->department_id->value(),
'reg_1' => $this->reg_1->value(),
'reg_2' => $this->reg_2->value(),
'reg_3' => $this->reg_3->value(),
'completed_at' => $this->completed_at->value(),
'created_at' => $this->created_at->value(),
'deleted_at' => $this->deleted_at->value(),
'total_net_amount' => $this->getNetAmount(),
'invoice_collection_id' => (int)$this->invoice_collection_id->value(),
'booking_id' => (int)$this->booking_id->value(),
'closed_at' => (int)$this->invoice_collection_id->value() ? (new collected_order_invoices_o())->select((int)$this->invoice_collection_id->value())->closed_at->value() : null,
];
}
public function getNetAmount(): float
{
self::requireSelected();
$net = 0;
// Get the order items object
$order_items = new order_items_o();
// Get the price, quantity of the order items
$items = $order_items->getAllItemsAsArray($this->id);
// Loop through the items and get the net amount
foreach ( $items as $item ) {
// Check if the item is included in the invoice
if (!$item['include_in_invoice']) {
continue;
}
$tmp_price = (int)$item['price'];
$tmp_quantity = (int)$item['quantity'];
// Add the price to the net amount
$net += $tmp_price * $tmp_quantity;
}
return $net;
}
public function exists(): bool
{
// Check if the id is greater than 0, and that the deleted_at property is null
if ($this->id > 0) {
$this->getObjectProperties();
return $this->deleted_at->value() === null;
}
return false;
}
public function getCustomerByOrderId(?string $order_id): users_o
{
global $db;
@@ -310,6 +186,16 @@ class orders_o extends db
$this->{$data['field']}->set($data['value']);
}
public function exists(): bool
{
// Check if the id is greater than 0, and that the deleted_at property is null
if ($this->id > 0) {
$this->getObjectProperties();
return $this->deleted_at->value() === null;
}
return false;
}
/**
* Mark the order as completed
* @throws Exception If the order is not selected
@@ -490,6 +376,21 @@ class orders_o extends db
return $order_items;
}
public function getOrderItems(int $order_id): array
{
global $db;
$sql = "SELECT * FROM order_items WHERE order_id = $order_id";
$result = $db->query($sql);
$order_items = [];
if ($result->num_rows > 0 && $result) {
while ($row = $result->fetch_assoc()) {
$order_item = new order_items_o();
$order_items[] = $order_item->getOrderItemById($row['id'])->getItemAsArray();
}
}
return $order_items;
}
/**
* Get the order history for a vehicle plate
* @param string $plate The vehicle plate
@@ -535,4 +436,126 @@ class orders_o extends db
}
return $order_collection;
}
/**
* Get the wash subscription transactions for a customer
* @throws Exception If something goes wrong
*/
public function getWashSubscriptionTransactions(int $customer_number): array
{
return self::listObjectsWithPagination(
1,
100,
null,
[
'customer_id' => $customer_number,
'deleted_at' => null,
'cashier_id' => (new collected_order_invoices_o())->economic_wash_subscription_user_id,
],
[
'id' => 'DESC',
],
function ($object) {
return (new orders_o())->select($object['id'])->includeIncludes()->asArray();
}
);
}
public function asArray(): array
{
return [
'id' => $this->id,
'customer_id' => (int)$this->customer_id->value(),
'cashier_id' => (int)$this->cashier_id->value(),
'reference' => $this->reference->value(),
'notes' => $this->notes->value(),
'department_id' => (int)$this->department_id->value(),
'reg_1' => $this->reg_1->value(),
'reg_2' => $this->reg_2->value(),
'reg_3' => $this->reg_3->value(),
'completed_at' => $this->completed_at->value(),
'created_at' => $this->created_at->value(),
'deleted_at' => $this->deleted_at->value(),
'total_net_amount' => $this->getNetAmount(),
'invoice_collection_id' => (int)$this->invoice_collection_id->value(),
'booking_id' => (int)$this->booking_id->value(),
'closed_at' => (int)$this->invoice_collection_id->value() ? (new collected_order_invoices_o())->select((int)$this->invoice_collection_id->value())->closed_at->value() : null,
];
}
public function getNetAmount(): float
{
self::requireSelected();
$net = 0;
// Get the order items object
$order_items = new order_items_o();
// Get the price, quantity of the order items
$items = $order_items->getAllItemsAsArray($this->id);
// Loop through the items and get the net amount
foreach ( $items as $item ) {
// Check if the item is included in the invoice
if (!$item['include_in_invoice']) {
continue;
}
$tmp_price = (int)$item['price'];
$tmp_quantity = (int)$item['quantity'];
// Add the price to the net amount
$net += $tmp_price * $tmp_quantity;
}
return $net;
}
public function includeIncludes(): orders_o
{
global /** @var response $response */
$response;
$includeEverything = $response->getRequestParameter('include_all') === 'true';
/** orderItems */
if ($response->getRequestParameter('includeOrderItems') || $includeEverything) {
$response->add_include('orderItems', $this->applyDepartmentPrices($this->getOrderItems($this->id), $this->department_id->value()));
}
/** customer */
if ($response->getRequestParameter('includeCustomer') || $includeEverything) {
$customer = new users_o();
$response->add_include('customer', $customer->getOrImportCustomerByCustomerNumber($this->customer_id->value())->includeIncludes()->asArray());
}
/** cashier */
if ($response->getRequestParameter('includeCashier') || $includeEverything) {
$cashier = new users_o();
$response->add_include('cashier', $cashier->getUserById($this->cashier_id->value())->asArray());
}
/**
* economicModuleOrders
*/
if ($response->getRequestParameter('includeEconomicModuleOrders') || $includeEverything) {
$response->add_include('economicModuleOrders', $this->economic_module_orders->getByOrderId($this->id)->asArray());
}
/**
* stripeModuleOrders
*/
if ($response->getRequestParameter('includeStripeModuleOrders') || $includeEverything) {
$response->add_include('stripeModuleOrders', $this->stripe_module_orders->exists() ? $this->stripe_module_orders->asArray() : []);
}
return $this;
}
/**
* Apply department pricing to a list of products
* @param array $order_items
* @param int $department_id
* @return array
*/
public function applyDepartmentPrices(array $order_items, int $department_id): array
{
global $response;
$department = new departments_o();
$department->getDepartmentById($department_id);
$department->getDepartmentProductPrices($department_id);
foreach ( $order_items as $key => $order_item ) {
$product = new products_o();
$product->getProductById($order_item['product_id']);
$order_items[$key]['product']['price'] = $product->getDepartmentPrice($department_id);
}
return $order_items;
}
}
+23
View File
@@ -35,6 +35,7 @@ class users_o extends db
protected object_property $phone_country_code;
protected object_property $phone;
protected object_property $email;
protected array $wash_subscription_transactions;
public function structure(): void
{
@@ -278,6 +279,10 @@ class users_o extends db
if (isset($this->all_keys)) {
$array['keys'] = $this->all_keys;
}
// If the wash subscription transactions are set, add them to the array
if (isset($this->wash_subscription_transactions)) {
$array['wash_subscription_transactions'] = $this->wash_subscription_transactions;
}
return $array;
}
@@ -479,6 +484,12 @@ class users_o extends db
if ($includeEverything || $response->getRequestParameter('includeKeys') === 'true' || in_array('keys', $includes)) {
$this->getAllKeys();
}
/**
* Wash subscription transactions
*/
if ($includeEverything || $response->getRequestParameter('includeWashSubscriptionTransactions') === 'true' || in_array('washSubscriptionTransactions', $includes)) {
$this->getWashSubscriptionTransactions();
}
return $this;
}
@@ -532,6 +543,18 @@ class users_o extends db
$this->all_keys = $this->keys->setUser($this->id)->getAllKeys();
}
/**
* Get the wash subscription transactions for the user
* @throws Exception If the user is not selected
*/
private function getWashSubscriptionTransactions(): void
{
self::requireSelected();
$orders_o = new orders_o();
$wash_subscription_transactions = $orders_o->getWashSubscriptionTransactions($this->customer_number->value());
$this->wash_subscription_transactions = $wash_subscription_transactions;
}
/**
* Get the group of the user
* @throws Exception If the user is not selected
@@ -6,6 +6,7 @@ use classes\authentication;
use classes\economic;
use classes\response;
use classes\router;
use Exception;
use objects\collected_order_invoices_o;
use objects\logs_o;
use objects\users_o;
@@ -296,6 +297,69 @@ class orderInvoicesRoute
]
);
/** Collected order invoices > Vehicle subscriptions > POST */
$this->post('/collected-invoices/vehicle-subscriptions/custom', function () {
global $response;
self::requirePermission('add_collected_invoice_vehicle_subscriptions');
$user = (new authentication())->get_user();
if ($user) {
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'ADD_COLLECTED_INVOICE_VEHICLE_SUBSCRIPTIONS', 'User added a collected order invoice for vehicle subscriptions');
// Require the ID, and validate its type and length
self::requireParameters(['customer_number', 'month', 'year']);
self::requireType((int)self::getParameter('customer_number'), self::type_int());
self::requireMinLength('customer_number', 1);
self::requireMaxLength('customer_number', 10);
self::requireMinValue((int)self::getParameter('customer_number'), 1);
$customer = (new users_o())->getUserByCustomerNumber((int)self::getParameter('customer_number'));
$customer->requireSelected();
// Require the month to be between 1 and 12
self::requireType((int)self::getParameter('month'), self::type_int());
self::requireMinLength('month', 1);
self::requireMaxLength('month', 2);
self::requireMinValue((int)self::getParameter('month'), 1);
self::requireMaxValue((int)self::getParameter('month'), 12);
// Require the year to be in the past 2 years
self::requireType((int)self::getParameter('year'), self::type_int());
self::requireMinLength('year', 1);
self::requireMaxLength('year', 4);
self::requireMinValue((int)self::getParameter('year'), date('Y') - 2);
self::requireMaxValue((int)self::getParameter('year'), date('Y'));
// Create the collected order invoice
$collected_order_invoices = new collected_order_invoices_o();
$collected_order_invoices->add(
(int)self::getParameter('customer_number'),
);
// Require the collected order invoice to be selected
$collected_order_invoices->requireSelected();
// Get the month and year from the request
$month = (int)self::getParameter('month');
$year = (int)self::getParameter('year');
// Add a leading zero to the month if it's less than 10
$month_with_prefix_if_applicable = str_pad($month, 2, '0', STR_PAD_LEFT);
// Add a leading zero to the year if it's less than 4 digits
$year_with_prefix_if_applicable = str_pad($year, 4, '0', STR_PAD_LEFT);
// Generate the timestamp for the selected month and year, with the first day, and first second
$timestamp_selected = mktime(0, 0, 1, $month_with_prefix_if_applicable, 1, $year_with_prefix_if_applicable);
// MySQL requires the date to be in the format YYYY-MM-DD HH:MM:SS
$timestamp_selected = date('Y-m-d H:i:s', $timestamp_selected);
// Set the date to the first second of the date specified
$collected_order_invoices->created_at->set($timestamp_selected);
// Add the collected order invoice to E-Conomic
$collected_order_invoices->addVehicleSubscriptionsTransaction();
// Close the collected order invoice
$collected_order_invoices->closeCollection();
// Return the collected order invoice
$response->success($collected_order_invoices->asArray());
} else {
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'ADD_COLLECTED_INVOICE_VEHICLE_SUBSCRIPTIONS', 'User tried to add a collected order invoice for vehicle subscriptions without a valid session');
$response->error('Invalid session', 400);
}
},
[
'add_collected_invoice_vehicle_subscriptions' => 'Add a collected order invoice for vehicle subscriptions. This is a superuser-only route.'
]
);
/** Collected order invoices > Open > GET Customers */
$this->get('/collected-invoices/customers', function () {
global $response;
@@ -645,7 +709,7 @@ class orderInvoicesRoute
try {
// Run the check drafts
$economic->getTasks()->runCheckDrafts();
} catch (\Exception $e) {
} catch (Exception $e) {
// If the task fails, log the error and return an error response
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'RUN_CHECK_DRAFTS', 'User tried to run the check drafts, but it failed: ' . $e->getMessage());
$response->error('Failed to run the check drafts: ' . $e->getMessage(), 500);
@@ -673,7 +737,7 @@ class orderInvoicesRoute
try {
// Run the check errors
$economic->getTasks()->runCheckErrors();
} catch (\Exception $e) {
} catch (Exception $e) {
// If the task fails, log the error and return an error response
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'RUN_CHECK_ERRORS', 'User tried to run the check errors, but it failed: ' . $e->getMessage());
$response->error('Failed to run the check errors: ' . $e->getMessage(), 500);
@@ -696,7 +760,7 @@ class orderInvoicesRoute
* @param users_o $users
* @param collected_order_invoices_o $tmp_collected_order_invoices
* @return array
* @throws \Exception
* @throws Exception
*/
function getOrderInvoiceDetails($collected_order_invoice, users_o $users, collected_order_invoices_o $tmp_collected_order_invoices): array
{
+15 -3
View File
@@ -34,6 +34,14 @@ class ordersRoute
// Create economic_module_orders object
$economic_module_orders = new economic_module_orders();
$orders = new orders_o();
$department_ids = $user->getGroup()->getDepartments();
if (self::isParametersSet(['show_wash_subscription'])) {
// Check if the boolean is true
if (self::getParameter('show_wash_subscription') === 'true') {
// add the '10' to the department_ids
$department_ids[] = '10';
}
}
// Return the list of departments
$orders->setView('orders_with_invoice_collections');
$response->success(
@@ -65,7 +73,7 @@ class ordersRoute
$orders->forceRestrictFilters(
[
// This makes sure that the user can only see orders from the departments they explicitly have access to
'department_id' => $user->getGroup()->getDepartments(),
'department_id' => $department_ids,
]
)
)
@@ -429,7 +437,7 @@ class ordersRoute
$this->delete('/orders/module/stripe/payment_intent', function () {
// Require the user to be logged in
global $response;
$this->requirePermission('delete_payment_intent');
$this->requirePermission('charge_order');
// Get the user object
$user = (new authentication())->get_user();
// Check if the request was successful
@@ -466,7 +474,11 @@ class ordersRoute
// Return an error
$response->error('Invalid session', 400);
}
});
},
[
'charge_order' => 'Delete a payment intent'
]
);
$this->post('/orders/module/stripe/payment_intent/capture', function () {
// Require the user to be logged in
+16
View File
@@ -4,6 +4,7 @@ namespace traits;
use classes\authentication;
use classes\recaptcha;
use classes\response;
use objects\logs_o;
trait route_t
@@ -162,6 +163,21 @@ trait route_t
}
}
/**
* Require the max value of the given value
* @param int $value - The value to check
* @param int $max - The max value
*/
public function requireMaxValue(int $value, int $max): void
{
global
/** @var response $response */
$response;
if ($value > $max) {
$response->error('Parameter must be at most ' . $max, 400);
}
}
/**
* Require department access
* @note This checks if the user has access to the department by checking if the user has the permission department_access_{department} (_{permission} if provided)