Files
api/services/nginx/app/routes/orderInvoicesRoute.php
T
Jeppe Bundgaard ab93866295 Add Redis connection checks and optimize caching for collected order invoices
- Enhance Redis methods (`exists`, `setEx`, `delete`, `get`, `set`) to ensure connection before execution.
- Introduce short-lived caching for collected order invoices to minimize redundant processing and improve performance.
- Add `pagination_helper` for dynamic WHERE clause construction in queries.
- Refactor net amount calculation in `collected_order_invoices_o` for efficiency with batch processing.
- Extend `listObjectsWithPaginationIfSet` to support additional WHERE clauses.
2026-02-03 11:43:56 +01:00

1043 lines
62 KiB
PHP

<?php
namespace routes;
use classes\authentication;
use classes\economic;
use classes\response;
use classes\router;
use Exception;
use objects\collected_order_invoices_o;
use objects\customer_fixed_pricing_o;
use objects\logs_o;
use objects\users_o;
use traits\route_t;
class orderInvoicesRoute
{
use route_t;
public function run(): void
{
global /** @var response $response */
/** @var router $router */
$router, $response;
/** Collected order invoices > GET */
$this->get('/collected-invoices', function () {
global $response;
self::requirePermission('list_collected_invoices');
$user = (new authentication())->get_user();
if ($user) {
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'LIST_COLLECTED_INVOICES', 'User accessed the list of collected order invoices');
$collected_order_invoices = new collected_order_invoices_o();
// Check if the request contains an ID
if (self::isParametersSet(['id'])) {
self::requireType((int)self::getParameter('id'), self::type_int());
$collected_order_invoices->select((int)self::getParameter('id'));
$collected_order_invoices->requireSelected();
$response->success($collected_order_invoices->asArray());
}
// Define the users
$users = new users_o();
// Define the collected order invoices
$tmp_collected_order_invoices = new collected_order_invoices_o();
// Return the list of collected order invoices
$startTime = microtime(true);
// Build a short-lived cache key to coalesce concurrent identical requests
$cacheTtl = 15; // seconds
$pageParam = (string)($response->getRequestParameter('page') ?? '1');
$limitParam = (string)($response->getRequestParameter('limit') ?? '1000');
$searchParam = (string)($response->getRequestParameter('search') ?? '');
$orderParam = (string)($response->getRequestParameter('order') ?? 'id:ASC');
$filtersParam = (string)($response->getRequestParameter('filters') ?? '');
$cacheKey = 'collected_invoices:list:' . md5(json_encode([
'p' => $pageParam,
'l' => $limitParam,
's' => $searchParam,
'o' => $orderParam,
'f' => $filtersParam,
], JSON_UNESCAPED_UNICODE));
$redis = new \classes\redis();
$cachedPayload = $redis->get($cacheKey);
if ($cachedPayload) {
// Cached payload contains both meta and data
$payload = json_decode($cachedPayload, true);
if (isset($payload['meta']) && is_array($payload['meta'])) {
foreach ($payload['meta'] as $k => $v) {
$response->add_meta($k, $v);
}
}
$durationMs = (int)round((microtime(true) - $startTime) * 1000);
(new logs_o())->add(
'orderInvoices',
'global',
1,
$user->id,
'LIST_COLLECTED_INVOICES_TIMING_CACHE_HIT',
'Duration(ms): ' . $durationMs . ', page=' . ((int)$response->getRequestParameter('page')) . ', limit=' . ((int)$response->getRequestParameter('limit')) . ', search=' . (string)($response->getRequestParameter('search') ?? '') . ', order=' . (string)($response->getRequestParameter('order') ?? '')
);
$response->success($payload['data'] ?? []);
}
// Cache miss: compute and cache
$result = $collected_order_invoices->listObjectsWithPaginationIfSet(
function ($collected_order_invoice) use ($tmp_collected_order_invoices, $users) {
// Select the orders for each collected order invoice
$tmp_collected_order_invoices->select((int)$collected_order_invoice['id']);
return $this->getOrderInvoiceDetails($collected_order_invoice, $users, $tmp_collected_order_invoices);
},
null,
[],
// Set the where, to where a non-deleted order is connected to the collected order invoice
(new pagination_helper())->where->addCondition(pagination_condition_where::CUSTOM('EXISTS (SELECT 1 FROM orders o WHERE o.invoice_collection_id = collected_order_invoices.id AND o.deleted_at IS NULL)'))
);
// Capture current pagination meta for caching
$meta = [
'pagination' => [
'page' => (int)$pageParam,
'per_page' => (int)$limitParam,
// We don't have the total directly here; the response object already has it,
// but we cache the meta as provided by the client for consistency.
]
];
// Store combined payload
$redis->setEx($cacheKey, json_encode([
'data' => $result,
'meta' => $meta,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), $cacheTtl);
$durationMs = (int)round((microtime(true) - $startTime) * 1000);
// Add timing log for performance monitoring
(new logs_o())->add(
'orderInvoices',
'global',
1,
$user->id,
'LIST_COLLECTED_INVOICES_TIMING',
'Duration(ms): ' . $durationMs . ', page=' . ((int)$response->getRequestParameter('page')) . ', limit=' . ((int)$response->getRequestParameter('limit')) . ', search=' . (string)($response->getRequestParameter('search') ?? '') . ', order=' . (string)($response->getRequestParameter('order') ?? '')
);
$response->success($result);
} else {
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'LIST_COLLECTED_INVOICES', 'User tried to access the list of collected order invoices without a valid session');
$response->error('Invalid session', 400);
}
},
[
'list_collected_invoices' => 'List ALL collected order invoices. This is a superuser-only route.'
]
);
/** Collected order invoices > Ready to invoice > GET */
$this->get('/collected-invoices/ready-to-invoice', function () {
global $response;
self::requirePermission('list_collected_invoices');
$user = (new authentication())->get_user();
if ($user) {
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'LIST_COLLECTED_INVOICES_READY_TO_INVOICE', 'User accessed the list of collected order invoices ready to invoice');
$collected_order_invoices = new collected_order_invoices_o();
// Define the users
$users = new users_o();
// Define the collected order invoices
$tmp_collected_order_invoices = new collected_order_invoices_o();
// Return the list of collected order invoices
$response->success($collected_order_invoices->listObjectsWithPaginationIfSet(
function ($collected_order_invoice) use ($tmp_collected_order_invoices, $users) {
// Select the orders for each collected order invoice
$tmp_collected_order_invoices->select((int)$collected_order_invoice['id']);
return $this->getOrderInvoiceDetails($collected_order_invoice, $users, $tmp_collected_order_invoices);
},
$collected_order_invoices->forceRestrictFilters(
[
// This makes sure that the user can only see orders from the departments they explicitly have access to
'customer_number' => $collected_order_invoices->listCustomersWithIndividualOrderInvoicing(),
]
)
));
} else {
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'LIST_COLLECTED_INVOICES_READY_TO_INVOICE', 'User tried to access the list of collected order invoices ready to invoice without a valid session');
$response->error('Invalid session', 400);
}
},
[
'list_collected_invoices' => 'List ALL collected order invoices. This is a superuser-only route.'
]
);
/** Collected order invoices > POST */
$this->post('/collected-invoices', function () {
global $response;
self::requirePermission('add_collected_invoice');
$user = (new authentication())->get_user();
if ($user) {
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'ADD_COLLECTED_INVOICE', 'User added a collected order invoice');
// Require the customer number, and validate its type and length
self::requireParameters(['customer_number']);
self::requireType((int)self::getParameter('customer_number'), self::type_int());
self::requireMinLength('customer_number', 1);
self::requireMaxLength('customer_number', 10);
// Require the customer number to be above 0
self::requireMinValue((int)self::getParameter('customer_number'), 1);
// Validate the customer number against the database
$customer = (new users_o())->select((int)self::getParameter('customer_number'));
$customer->requireSelected();
// Define the variables
$name = null;
$notes = null;
$processor = null;
$closed_at = null;
// Check if the name is set
if (self::isParametersSet(['name'])) {
self::requireType((string)self::getParameter('name'), self::type_string());
self::requireMinLength('name', 1);
self::requireMaxLength('name', 255);
$name = (string)self::getParameter('name');
}
// Check if the notes are set
if (self::isParametersSet(['notes'])) {
self::requireType((string)self::getParameter('notes'), self::type_string());
$notes = (string)self::getParameter('notes');
}
// Check if the processor is set
if (self::isParametersSet(['processor'])) {
self::requireType((int)self::getParameter('processor'), self::type_int());
$processor = (int)self::getParameter('processor');
}
// Check if the closed_at is set
if (self::isParametersSet(['closed_at'])) {
// Check if the closed date is set, and validate its type
if (!!self::getParameter('closed_at')) {
self::requireType((string)self::getParameter('closed_at'), self::type_string());
$closed_at = (string)self::getParameter('closed_at');
// Check if the closed date is valid
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $closed_at)) {
$response->error('Invalid closed date format', 400);
}
// Set the time to 00:00:01z
$closed_at = date('Y-m-d H:i:s', strtotime($closed_at . ' 00:00:01'));
}
}
// Add the collected order invoice
$collected_order_invoices = new collected_order_invoices_o();
$collected_order_invoices->add(
$customer->id,
$name,
$notes,
$processor,
$closed_at ?: null,
);
$response->success($collected_order_invoices->asArray());
} else {
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'ADD_COLLECTED_INVOICE', 'User tried to add a collected order invoice without a valid session');
$response->error('Invalid session', 400);
}
},
[
'add_collected_invoice' => 'Add a collected order invoice. This is a superuser-only route.'
]
);
/** Collected order invoices > Split > POST */
$this->post('/collected-invoices/split', function () {
global $response;
self::requirePermission('split_collected_invoice');
$user = (new authentication())->get_user();
if ($user) {
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'SPLIT_COLLECTED_INVOICE', 'User split a collected order invoice');
// Require the ID, and validate its type and length
self::requireParameters(['id']);
self::requireType((int)self::getParameter('id'), self::type_int());
self::requireMinValue((int)self::getParameter('id'), 1);
// Require the ID to be above 0
self::requireMinValue((int)self::getParameter('id'), 1);
// Validate the ID against the database
$collected_order_invoices = (new collected_order_invoices_o())->select((int)self::getParameter('id'));
$collected_order_invoices->requireSelected();
// Split the collected order invoice
$collected_order_invoices->split();
// Return the collected order invoice
$response->success($collected_order_invoices->asArray());
} else {
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'SPLIT_COLLECTED_INVOICE', 'User tried to split a collected order invoice without a valid session');
$response->error('Invalid session', 400);
}
},
[
'split_collected_invoice' => 'Split a collected order invoice. This is a superuser-only route.'
]
);
/** Collected order invoices > E-Conomic > POST */
$this->post('/collected-invoices/economic', function () {
global $response;
self::requirePermission('add_collected_invoice_economic');
$user = (new authentication())->get_user();
if ($user) {
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'ADD_COLLECTED_INVOICE_ECONOMIC', 'User added a collected order invoice to E-Conomic');
// Require the ID, and validate its type and length
self::requireParameters(['id']);
self::requireType((int)self::getParameter('id'), self::type_int());
self::requireMinLength('id', 1);
self::requireMaxLength('id', 10);
// Require the ID to be above 0
self::requireMinValue((int)self::getParameter('id'), 1);
// If the send_as_is parameter is set, validate its type
$send_as_is = false; // This is false by default, when true it will send the invoice as is, without adding vehicle subscriptions or fixed pricing. This is used when a customer with fixed pricing has already been invoiced with the fixed price, and we just need to send the invoice to E-Conomic.
if (self::isParametersSet(['send_as_is'])) {
self::requireType((bool)self::getParameter('send_as_is'), self::type_bool());
$send_as_is = (bool)self::getParameter('send_as_is');
}
// Validate the ID against the database
$collected_order_invoices = (new collected_order_invoices_o())->select((int)self::getParameter('id'));
$collected_order_invoices->requireSelected();
// Check if the collected order invoice has an external ID
if ($collected_order_invoices->external_id->value() === null) {
// Check if the send_as_is parameter is set to true
if (!$send_as_is) {
// Add fixed price to the collected order invoice
$customer_fixed_pricing_o = new customer_fixed_pricing_o();
if ($customer_fixed_pricing_o->doesUserHaveFixedPricing((int)$collected_order_invoices->customer_number->value())) {
$customer_fixed_pricing_price = $customer_fixed_pricing_o->selectByCustomerNumber((int)$collected_order_invoices->customer_number->value());
$customer_fixed_pricing_price = (int)$customer_fixed_pricing_price->price->value();
$collected_order_invoices->overridePricesFixed((int)$customer_fixed_pricing_price);
} else {
// Apply vehicle subscriptions if the customer does not have fixed pricing
$collected_order_invoices->addVehicleSubscriptionsTransaction();
}
} else {
// Since we are sending the invoice as is, we need to check if the invoice has any left-over subscription / fixed price transactions
$collected_order_invoices->removeSpecialArrangements(); // Remove any left-over special arrangement transactions (subscriptions / fixed price)
// Reset the price of all items set not to be included in the invoice
//$collected_order_invoices->resetPricesOfItemsNotIncludedInInvoice();
// Set all items to be included in the invoice
$collected_order_invoices->setAllItemsToBeIncludedInInvoice(); // Set all items to be included in the invoice, since we are sending the invoice as is.
}
// Add the collected order invoice to E-Conomic
$collected_order_invoices->addToEconomic();
$response->success($collected_order_invoices->asArray());
}
// Check if the invoice has been booked
if ($collected_order_invoices->booked_invoice_id->value() !== null) {
$response->error('Invoice has already been booked', 400);
}
// Check if the invoice draft exists in E-Conomic
if ($collected_order_invoices->isDraftExisting()) {
$response->error('Invoice draft already exists in E-Conomic', 400);
}
// Add fixed price to the collected order invoice
$customer_fixed_pricing_o = new customer_fixed_pricing_o();
if ($customer_fixed_pricing_o->doesUserHaveFixedPricing((int)$collected_order_invoices->customer_number->value())) {
$customer_fixed_pricing_price = $customer_fixed_pricing_o->selectByCustomerNumber((int)$collected_order_invoices->customer_number->value());
$customer_fixed_pricing_price = (int)$customer_fixed_pricing_price->price->value();
$collected_order_invoices->overridePricesFixed((int)$customer_fixed_pricing_price);
} else {
// Apply vehicle subscriptions if the customer does not have fixed pricing
$collected_order_invoices->addVehicleSubscriptionsTransaction();
}
// Create the invoice in E-Conomic
$collected_order_invoices->addToEconomic(true);
// Return the collected order invoice
$response->success($collected_order_invoices->asArray());
} else {
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'ADD_COLLECTED_INVOICE_ECONOMIC', 'User tried to add a collected order invoice to E-Conomic without a valid session');
$response->error('Invalid session', 400);
}
},
[
'add_collected_invoice_economic' => 'Add a collected order invoice to E-Conomic. This is a superuser-only route.'
]
);
/** Collected order invoices > E-Conomic > Unlink and clear cached data > POST */
$this->post('/collected-invoices/economic/unlink', function () {
global $response;
self::requirePermission('unlink_collected_invoice_economic');
$user = (new authentication())->get_user();
if ($user) {
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'UNLINK_COLLECTED_INVOICE_ECONOMIC', 'User unlinked a collected order invoice from E-Conomic');
// Require the ID, and validate its type and length
self::requireParameters(['id']);
self::requireType((int)self::getParameter('id'), self::type_int());
self::requireMinLength('id', 1);
self::requireMaxLength('id', 10);
// Require the ID to be above 0
self::requireMinValue((int)self::getParameter('id'), 1);
// Validate the ID against the database
$collected_order_invoices = (new collected_order_invoices_o())->select((int)self::getParameter('id'));
$collected_order_invoices->requireSelected();
// Unlink the collected order invoice from E-Conomic
$collected_order_invoices->unlinkFromEconomic();
// Return the collected order invoice
$response->success($collected_order_invoices->asArray());
} else {
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'UNLINK_COLLECTED_INVOICE_ECONOMIC', 'User tried to unlink a collected order invoice from E-Conomic without a valid session');
$response->error('Invalid session', 400);
}
},
[
'unlink_collected_invoice_economic' => 'Unlink a collected order invoice from E-Conomic. This is a superuser-only route. This will NOT delete the invoice in E-Conomic, but will clear the cached data in the system.'
]
);
/** Collected order invoices > E-Conomic > Remove special arrangements, and set all items to be included in the invoice > POST */
$this->post('/collected-invoices/remove-special-arrangements', function () {
global $response;
self::requirePermission('reset_collected_invoice_economic');
$user = (new authentication())->get_user();
if ($user) {
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'RESET_COLLECTED_INVOICE_ECONOMIC', 'User reset a collected order invoice in E-Conomic');
// Require the ID, and validate its type and length
self::requireParameters(['id']);
self::requireType((int)self::getParameter('id'), self::type_int());
self::requireMinLength('id', 1);
self::requireMaxLength('id', 10);
// Require the ID to be above 0
self::requireMinValue((int)self::getParameter('id'), 1);
// Validate the ID against the database
$collected_order_invoices = (new collected_order_invoices_o())->select((int)self::getParameter('id'));
$collected_order_invoices->requireSelected();
// Remove special arrangements, and set all items to be included in the invoice
$collected_order_invoices->removeSpecialArrangements(); // Remove any left-over special arrangement transactions (subscriptions / fixed price)
//$collected_order_invoices->resetPricesOfItemsNotIncludedInInvoice(); // Reset the price of all items set not to be included in the invoice
$collected_order_invoices->setAllItemsToBeIncludedInInvoice(); // Set all items to be included in the invoice
$collected_order_invoices->objectChanged();
// Return the collected order invoice
$response->success($collected_order_invoices->asArray());
} else {
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'RESET_COLLECTED_INVOICE_ECONOMIC', 'User tried to reset a collected order invoice in E-Conomic without a valid session');
$response->error('Invalid session', 400);
}
},
[
'reset_collected_invoice_economic' => 'Reset a collected order invoice in E-Conomic. This is a superuser-only route. This will NOT delete the invoice in E-Conomic, but will remove any special arrangement transactions (subscriptions / fixed price), and set all items to be included in the invoice.'
]
);
/** Collected order invoices > E-Conomic > Reset prices of items not included in the invoice > POST */
$this->post('/collected-invoices/reset-prices-of-items-not-included-in-invoice', function () {
global $response;
self::requirePermission('reset_collected_invoice_economic');
$user = (new authentication())->get_user();
if ($user) {
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'RESET_COLLECTED_INVOICE_ECONOMIC', 'User reset a collected order invoice in E-Conomic');
// Require the ID, and validate its type and length
self::requireParameters(['id']);
self::requireType((int)self::getParameter('id'), self::type_int());
self::requireMinLength('id', 1);
self::requireMaxLength('id', 10);
// Require the ID to be above 0
self::requireMinValue((int)self::getParameter('id'), 1);
// Validate the ID against the database
$collected_order_invoices = (new collected_order_invoices_o())->select((int)self::getParameter('id'));
$collected_order_invoices->requireSelected();
// Reset prices of items not included in the invoice
$collected_order_invoices->resetPricesOfItemsNotIncludedInInvoice(); // Reset the price of all items set not to be included in the invoice
$collected_order_invoices->objectChanged();
// Return the collected order invoice
$response->success($collected_order_invoices->asArray());
} else {
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'RESET_COLLECTED_INVOICE_ECONOMIC', 'User tried to reset a collected order invoice in E-Conomic without a valid session');
$response->error('Invalid session', 400);
}
},
[
'reset_collected_invoice_economic' => 'Reset a collected order invoice in E-Conomic. This is a superuser-only route. This will NOT delete the invoice in E-Conomic, but will reset the prices of all items set not to be included in the invoice.'
]
);
/** Collected order invoices > Stripe > BOOK > POST */
$this->post('/collected-invoices/stripe/book', function () {
global $response;
self::requirePermission('add_collected_invoice_stripe');
$user = (new authentication())->get_user();
if ($user) {
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'ADD_COLLECTED_INVOICE_STRIPE', 'User added a collected order invoice to Stripe');
// Require the ID, and validate its type and length
self::requireParameters(['id']);
self::requireType((int)self::getParameter('id'), self::type_int());
self::requireMinLength('id', 1);
self::requireMaxLength('id', 10);
// Require the ID to be above 0
self::requireMinValue((int)self::getParameter('id'), 1);
// Validate the ID against the database
$collected_order_invoices = (new collected_order_invoices_o())->select((int)self::getParameter('id'));
$collected_order_invoices->requireSelected();
// Require the external ID to be set
if ($collected_order_invoices->external_id->value() === null) {
$response->error('Transaction has not been created in Stripe', 400);
}
// Require the booked invoice ID to be not set
if ($collected_order_invoices->booked_invoice_id->value() !== null) {
$response->error('Invoice has already been booked', 400);
}
// Add the collected order invoice to Stripe
$collected_order_invoices->addToEconomic(true);
// Return the collected order invoice
$response->success($collected_order_invoices->asArray());
} else {
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'ADD_COLLECTED_INVOICE_STRIPE', 'User tried to add a collected order invoice to Stripe without a valid session');
$response->error('Invalid session', 400);
}
},
[
'add_collected_invoice_stripe' => 'Add a collected order invoice to Stripe. This is a superuser-only route.'
]
);
/** Collected order invoices > Vehicle subscriptions > POST */
$this->post('/collected-invoices/vehicle-subscriptions', 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(['id']);
self::requireType((int)self::getParameter('id'), self::type_int());
self::requireMinLength('id', 1);
self::requireMaxLength('id', 10);
// Require the ID to be above 0
self::requireMinValue((int)self::getParameter('id'), 1);
// Validate the ID against the database
$collected_order_invoices = (new collected_order_invoices_o())->select((int)self::getParameter('id'));
$collected_order_invoices->requireSelected();
// Add the collected order invoice to E-Conomic
$collected_order_invoices->addVehicleSubscriptionsTransaction();
// 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 > Vehicle subscriptions > POST */
$this->post('/collected-invoices/fixed-price', function () {
global $response;
self::requirePermission('add_collected_invoice_fixed_price');
$user = (new authentication())->get_user();
if ($user) {
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'ADD_COLLECTED_INVOICE_FIXED_PRICE', 'User added a collected order invoice fixed price modifications');
// Require the ID, and validate its type and length
self::requireParameters(['id']);
self::requireType((int)self::getParameter('id'), self::type_int());
self::requireMinLength('id', 1);
self::requireMaxLength('id', 10);
// Require the ID to be above 0
self::requireMinValue((int)self::getParameter('id'), 1);
// Validate the ID against the database
$collected_order_invoices = (new collected_order_invoices_o())->select((int)self::getParameter('id'));
$collected_order_invoices->requireSelected();
// Check if the customer has a fixed price
$customer = new users_o();
$customer->getUserByCustomerNumber((int)$collected_order_invoices->customer_number->value());
$customer_fixed_pricing_o = new customer_fixed_pricing_o();
if (!$customer_fixed_pricing_o->doesUserHaveFixedPricing((int)$customer->customer_number->value())) {
$response->error('Customer does not have fixed pricing', 400);
}
$customer_fixed_pricing_price = $customer_fixed_pricing_o->selectByCustomerNumber((int)$customer->customer_number->value());
$customer_fixed_pricing_price = (int)$customer_fixed_pricing_price->price->value();
// Add the collected order invoice to E-Conomic
$collected_order_invoices->overridePricesFixed((int)$customer_fixed_pricing_price);
// Return the collected order invoice
$response->success($collected_order_invoices->asArray());
} else {
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'ADD_COLLECTED_INVOICE_FIXED_PRICE', 'User tried to add a collected order invoice fixed price modifications without a valid session');
$response->error('Invalid session', 400);
}
},
[
'add_collected_invoice_fixed_price' => 'Add a collected order invoice fixed price modifications. This is a superuser-only route.'
]
);
/** 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();
// This
// Close the collected order invoice
$collected_order_invoices->closed_at->set($timestamp_selected);
// Update the collected order invoice
$collected_order_invoices->objectChanged();
// 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;
self::requirePermission('list_collected_invoices');
$user = (new authentication())->get_user();
if ($user) {
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'LIST_COLLECTED_INVOICES_CUSTOMERS', 'User accessed the list of collected order invoices customers');
// Define the users
$users_o = new users_o();
$tmp_customer_numbers = $users_o->getCustomerNumbersWithAttributes([
'invoiceAllOrdersIndividually'
]);
// Filter out the customers that do not have any open invoices
$customer_numbers = [];
//print_r($tmp_customer_numbers);
foreach ( $tmp_customer_numbers as $customer_number ) {
// This is the customers open invoices, this might contain invoices that's empty, or only contains deleted orders.
$tmp_customer_open_invoice_ids = [];
// This is the net amount of the total transactions for the customer
$tmp_total_net_amount = 0;
// This is the number of invoices, that's not empty.
$tmp_customer_active_invoice_ids = [];
$collected_order_invoices = new collected_order_invoices_o();
$customer_open_invoices = $collected_order_invoices->listObjectsWithPaginationIfSet(
function ($collected_order_invoice) use ($tmp_total_net_amount, $users_o, $collected_order_invoices) {
// Select the orders for each collected order invoice
$collected_order_invoices->select((int)$collected_order_invoice['id']);
// Return the details
return $this->getOrderInvoiceDetails($collected_order_invoice, $users_o, $collected_order_invoices);
},
$collected_order_invoices->forceRestrictFilters([
'customer_number' => $customer_number,
'closed_at' => 'null',
])
);
//print_r($customer_open_invoices);
// Check if the customer has any open invoices
if (count($customer_open_invoices) > 0) {
$tmp_customer_orders = 0;
// Get the total net amount of the open invoices
foreach ( $customer_open_invoices as $customer_open_invoice ) {
// Check if the invoice is empty
if (count($customer_open_invoice['orders']) === 0) {
continue;
}
// Add the invoice collected order invoice ID to the list (if it's not already in the list)
if (!in_array($customer_open_invoice['id'], $tmp_customer_open_invoice_ids)) {
$tmp_customer_active_invoice_ids[] = $customer_open_invoice['id'];
}
// Update the total net amount, and the number of orders
$tmp_customer_open_invoice_ids[] = $customer_open_invoice['id'];
$tmp_total_net_amount += $customer_open_invoice['total_net_amount'];
$tmp_customer_orders += count($customer_open_invoice['orders']);
}
// If the open invoices are not empty, add the customer to the list
if (empty($tmp_customer_orders)) {
continue;
}
$tmp_user = (new users_o())->getUserByCustomerNumber($customer_number);
$customer_numbers[] = array(
'customer_number' => $customer_number,
'customer_name' => $users_o->getCustomerName($customer_number),
'user_id' => $tmp_user->id,
'open_invoices' => $customer_open_invoices,
'active_invoices' => $tmp_customer_active_invoice_ids,
'orders' => $tmp_customer_orders,
'total_net_amount' => $tmp_total_net_amount,
);
}
}
// Return the list of customers
$response->success($customer_numbers);
} else {
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'LIST_COLLECTED_INVOICES_CUSTOMERS', 'User tried to access the list of collected order invoices customers without a valid session');
$response->error('Invalid session', 400);
}
},
[
'list_collected_invoices_customers' => 'List ALL collected order invoices customers. This is a superuser-only route.'
]
);
$this->get('/collected-invoices/customers/invoicePerOrder', function () {
global $response;
self::requirePermission('list_collected_invoices');
$user = (new authentication())->get_user();
if ($user) {
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'LIST_COLLECTED_INVOICES_CUSTOMERS_INVOICE_PER_ORDER', 'User accessed the list of collected order invoices customers with invoice per order');
// Define the users
$users_o = new users_o();
$users_o->setView('customers_invoice_count_per_order');
$result = $users_o
->setSearchableFields([
'customer_number',
'customer_name',
'active_invoices',
'closed_invoices',
])
->listObjectsWithPaginationIfSet(
function ($collected_order_invoice) use ($users_o) {
return [
'customer_number' => (int)$collected_order_invoice['customer_number'],
'display_name' => (string)$users_o->getCustomerName((int)$collected_order_invoice['customer_number']),
'active_invoices' => (int)$collected_order_invoice['active_invoices'],
'closed_invoices' => (int)$collected_order_invoice['closed_invoices'],
];
},
$users_o->forceRestrictFilters(
[
//'active_invoices' => 'NOT ZERO',
]
)
);
// Return the list of customers
$response->success($result);
} else {
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'LIST_COLLECTED_INVOICES_CUSTOMERS_INVOICE_PER_ORDER', 'User tried to access the list of collected order invoices customers with invoice per order without a valid session');
$response->error('Invalid session', 400);
}
},
[
'list_collected_invoices_customers_invoice_per_order' => 'List ALL collected order invoices customers with invoice per order. This is a superuser-only route.'
]
);
$this->get('/collected-invoices/customers/invoicePerMonth', function () {
global $response;
self::requirePermission('list_collected_invoices');
$user = (new authentication())->get_user();
if ($user) {
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'LIST_COLLECTED_INVOICES_CUSTOMERS_INVOICE_PER_MONTH', 'User accessed the list of collected order invoices customers with invoice per month');
// Define the users
$users_o = new users_o();
$users_o->setView('customers_invoice_count_per_month');
$result = $users_o
->setSearchableFields([
'customer_number',
'customer_name',
'active_invoices',
'closed_invoices',
])
->listObjectsWithPaginationIfSet(
function ($collected_order_invoice) use ($users_o) {
return [
'customer_number' => (int)$collected_order_invoice['customer_number'],
'display_name' => (string)$users_o->getCustomerName((int)$collected_order_invoice['customer_number']),
'active_invoices' => (int)$collected_order_invoice['active_invoices'],
'closed_invoices' => (int)$collected_order_invoice['closed_invoices'],
];
},
$users_o->forceRestrictFilters(
[
//'active_invoices' => 'NOT ZERO',
]
)
);
// Return the list of customers
$response->success($result);
} else {
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'LIST_COLLECTED_INVOICES_CUSTOMERS_INVOICE_PER_MONTH', 'User tried to access the list of collected order invoices customers with invoice per month without a valid session');
$response->error('Invalid session', 400);
}
},
[
'list_collected_invoices_customers_invoice_per_month' => 'List ALL collected order invoices customers with invoice per month. This is a superuser-only route.'
]
);
$this->post('/collected-invoices/customers/invoiceTotals', function () {
// This is a superuser-only route
global $response;
self::requirePermission('list_collected_invoices');
$user = (new authentication())->get_user();
if ($user) {
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'LIST_COLLECTED_INVOICES_CUSTOMERS_INVOICE_TOTALS', 'User accessed the list of collected order invoices customers with invoice totals');
// Require the customer_numbers parameter
self::requireParameters(['customer_numbers']);
// Print the customer_numbers parameter
$customer_numbers = self::getParameter('customer_numbers');
// validate the customer_numbers parameter
if (!is_array($customer_numbers)) {
$response->error('customer_numbers must be an array', 400);
}
// Validate the customer_numbers parameter
foreach ( $customer_numbers as $customer_number ) {
self::requireType((int)$customer_number, self::type_int());
if (!is_numeric($customer_number)) {
$response->error('customer_numbers must be an array of integers', 400);
}
if (!$customer_number || $customer_number < 1) {
$response->error('customer_numbers must be an array of integers greater than 0', 400);
}
}
// Now we can safely use the customer_numbers parameter
// Define the result array
$result = [];
// Loop through the customer_numbers array, and get the invoice totals for each customer
foreach ( $customer_numbers as $customer_number ) {
// Define the invoices array
$tmp_invoices = [
'closed_invoices' => [],
'open_invoices' => [],
];
// Define the total net amount
$tmp_total_net_amount = [
'closed_invoices' => 0,
'open_invoices' => 0,
];
// Define the collected order invoices
$collected_order_invoices = new collected_order_invoices_o();
// Get the collected order invoices for the customer
$collected_order_invoices->setView('invoices_with_completed_orders');
$tmp_invoice_collections = $collected_order_invoices->listObjectsWithPaginationIfSet(
function ($collected_order_invoice) use ($tmp_invoices, $customer_number, $collected_order_invoices) {
// Select the orders for each collected order invoice
$collected_order_invoices->select((int)$collected_order_invoice['id']);
// Get the details for the collected order invoice
return $this->getOrderInvoiceDetails($collected_order_invoice, new users_o(), $collected_order_invoices);
},
$collected_order_invoices->forceRestrictFilters([
'customer_number' => $customer_number,
])
);
// Loop through the collected order invoices, and get the total net amount
foreach ( $tmp_invoice_collections as $tmp_invoice_collection ) {
// Update the total net amount
if ($tmp_invoice_collection['closed_at'] !== null) {
$tmp_total_net_amount['closed_invoices'] += $tmp_invoice_collection['total_net_amount'];
// Add the closed invoice to the closed invoices array
$tmp_invoices['closed_invoices'][] = $tmp_invoice_collection;
} else {
$tmp_total_net_amount['open_invoices'] += $tmp_invoice_collection['total_net_amount'];
// Add the open invoice to the open invoices array
$tmp_invoices['open_invoices'][] = $tmp_invoice_collection;
}
}
$tmp_user = (new users_o())->getUserByCustomerNumber($customer_number);
// Add the customer to the result array
$result[$customer_number] = [
'customer_number' => (int)$customer_number,
'customer_name' => (string)$tmp_user->display_name->value(),
'user_id' => (int)$tmp_user->id,
'invoices' => $tmp_invoices,
'total_net_amount' => $tmp_total_net_amount,
];
}
// Return the list of customers
$response->success($result);
} else {
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'LIST_COLLECTED_INVOICES_CUSTOMERS_INVOICE_TOTALS', 'User tried to access the list of collected order invoices customers with invoice totals without a valid session');
$response->error('Invalid session', 400);
}
});
$this->get('/collected-invoices/economic/overview', function () {
global $response;
self::requirePermission('list_collected_invoices_economic_overview');
$user = (new authentication())->get_user();
if ($user) {
// Get the economic module
$economic = new economic();
// Get the collected order invoices
$collected_order_invoices = new collected_order_invoices_o();
/**
* Get the total invoices using E-conomic as the processor, with the given restrictions
* @param $collected_order_invoices collected_order_invoices_o
* @param $restrictions array
* @param $view string The MySQL view to use for the query
* @param $page int The page number to return
* @param $limit int The number of results to return per page
* @return int
*/
function getTotalInvoices(collected_order_invoices_o $collected_order_invoices, array $restrictions = [], string $view = 'invoices_with_completed_orders', int $page = 1, int $limit = 100000): int
{
// Add the economic processor to the restrictions
$restrictions['processor'] = 1; // E-conomic
return count(
$collected_order_invoices->getTotalInvoices(
$collected_order_invoices,
$restrictions,
$view,
$page,
$limit,
1
)
);
}
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'LIST_COLLECTED_INVOICES_ECONOMIC_OVERVIEW', 'User accessed the list of collected order invoices economic overview');
// Define the result array
$result = [
'total' => getTotalInvoices($collected_order_invoices),
'paid' => 0,
'unpaid' => 0,
'overdue' => 0,
'booked' => getTotalInvoices(
$collected_order_invoices,
[
'booked_invoice_id' => 'NOT NULL',
'external_id' => 'NOT NULL',
],
'invoices_with_completed_orders'
),
'draft' => getTotalInvoices(
$collected_order_invoices,
[
'booked_invoice_id' => null,
'error_message' => null,
'external_id' => 'NOT NULL',
],
'invoices_with_completed_orders'
),
'sent' => 0,
'error' => getTotalInvoices(
$collected_order_invoices,
[
'booked_invoice_id' => null,
'external_id' => 'NOT NULL',
'error_message' => 'NOT NULL',
],
'invoices_with_completed_orders'
),
];
// Return the list of customers
$response->success($result);
} else {
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'LIST_COLLECTED_INVOICES_ECONOMIC_OVERVIEW', 'User tried to access the list of collected order invoices economic overview without a valid session');
$response->error('Invalid session', 400);
}
},
[
'list_collected_invoices_economic_overview' => 'List ALL collected order invoices overview. This is a superuser-only route.'
]
);
$this->post('/collected-invoices/economic/run/check-drafts', function () {
global $response;
self::requirePermission('module_economic_run_check_drafts');
$user = (new authentication())->get_user();
if ($user) {
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'RUN_CHECK_DRAFTS', 'User ran the check drafts');
// Get the economic module
$economic = new economic();
try {
// Run the check drafts
$economic->getTasks()->runCheckDrafts();
} 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);
}
// Return the result
$response->success('Check drafts completed successfully');
} else {
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'RUN_CHECK_DRAFTS', 'User tried to run the check drafts without a valid session');
$response->error('Invalid session', 400);
}
},
[
'module_economic_run_check_drafts' => 'Run the check drafts. This is a superuser-only route.'
]
);
$this->post('/collected-invoices/economic/run/check-errors', function () {
global $response;
self::requirePermission('module_economic_run_check_errors');
$user = (new authentication())->get_user();
if ($user) {
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'RUN_CHECK_ERRORS', 'User ran the check errors');
// Get the economic module
$economic = new economic();
try {
// Run the check errors
$economic->getTasks()->runCheckErrors();
} 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);
}
// Return the result
$response->success('Check errors completed successfully');
} else {
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'RUN_CHECK_ERRORS', 'User tried to run the check errors without a valid session');
$response->error('Invalid session', 400);
}
},
[
'module_economic_run_check_errors' => 'Run the check errors. This is a superuser-only route.'
]
);
}
/**
* @param $collected_order_invoice
* @param users_o $users
* @param collected_order_invoices_o $tmp_collected_order_invoices
* @return array
* @throws Exception
*/
function getOrderInvoiceDetails($collected_order_invoice, users_o $users, collected_order_invoices_o $tmp_collected_order_invoices): array
{
return [
'id' => (int)$collected_order_invoice['id'],
'name' => (string)$collected_order_invoice['name'],
'notes' => (string)$collected_order_invoice['notes'],
'customer_number' => (int)$collected_order_invoice['customer_number'],
'customer_name' => (string)$users->getCustomerName((int)$collected_order_invoice['customer_number']),
'processor' => $collected_order_invoice['processor'] ? (int)$collected_order_invoice['processor'] : null,
'external_id' => (string)$collected_order_invoice['external_id'],
'closed_at' => $collected_order_invoice['closed_at'] ? (string)$collected_order_invoice['closed_at'] : null,
'updated_at' => (string)$collected_order_invoice['updated_at'],
'created_at' => (string)$collected_order_invoice['created_at'],
'orders' => $tmp_collected_order_invoices->getOrders(),
'total_net_amount' => (float)$tmp_collected_order_invoices->getTotalAmount(),
];
}
}