Files
api/services/nginx/app/objects/orders_o.php
T

1051 lines
42 KiB
PHP

<?php
namespace objects;
use classes\db;
use classes\motorapi;
use classes\object_property;
use classes\response;
use Exception;
use helpers\xlvask_usage_log;
use helpers\xlvask_wash_item;
use routes\InvoicingPeriodRoute;
use traits\db_object_t;
class orders_o extends db
{
use db_object_t;
public object_property $customer_id;
public object_property $cashier_id;
public object_property $reference;
public object_property $notes;
public object_property $department_id;
public object_property $reg_1;
public object_property $reg_2;
public object_property $reg_3;
public economic_module_orders $economic_module_orders;
public object_property $created_at;
public object_property $deleted_at;
public object_property $completed_at;
public stripe_module_orders_o $stripe_module_orders;
public object_property $invoice_collection_id;
public object_property $booking_id;
public object_property $wash_id; // The XL Vask Wash ID, if any
public object_property $lane; // The lane used for the order, if any
/**
* Temporary keys
* These are used for calculations and should not be stored in the database.
* @var float $temporary_net_amount
* @see InvoicingPeriodRoute::getInvoicingPeriod()
*/
public float $temporary_net_amount = 0.0; // Temporary net amount for the order, used for calculations
public function structure(): void
{
$this->setTable('orders');
}
public function edit(int $id, int $customer_id, int $cashier_id, string $reference, string $notes, int $department_id): void
{
global $db, $response;
$this->id = $id;
try {
// Avoid SQL injection
$reference = $db->escape_string($reference);
$notes = $db->escape_string($notes);
// Update the record in the database
$sql = "UPDATE $this->table SET customer_id = $customer_id, cashier_id = $cashier_id, reference = '$reference', notes = '$notes', department_id = $department_id WHERE id = $id";
$db->query($sql);
// Set the values of the object properties
$this->getObjectProperties();
} catch (Exception $e) {
$response->error($e->getMessage());
}
}
public function getObjectProperties(): void
{
$this->customer_id = new object_property($this->table, $this->id, 'customer_id', 'int', true);
$this->cashier_id = new object_property($this->table, $this->id, 'cashier_id', 'int', true);
$this->reference = new object_property($this->table, $this->id, 'reference', 'string', true);
$this->notes = new object_property($this->table, $this->id, 'notes', 'string', false);
$this->department_id = new object_property($this->table, $this->id, 'department_id', 'int', true);
$this->reg_1 = new object_property($this->table, $this->id, 'reg_1', 'string', false);
$this->reg_2 = new object_property($this->table, $this->id, 'reg_2', 'string', false);
$this->reg_3 = new object_property($this->table, $this->id, 'reg_3', 'string', false);
$this->created_at = new object_property($this->table, $this->id, 'created_at', 'timestamp', false);
$this->completed_at = new object_property($this->table, $this->id, 'completed_at', 'timestamp', false);
$this->economic_module_orders = (new economic_module_orders())->getByOrderId($this->id);
$this->stripe_module_orders = (new stripe_module_orders_o())->select($this->id);
$this->deleted_at = new object_property($this->table, $this->id, 'deleted_at', 'timestamp', false);
$this->invoice_collection_id = new object_property($this->table, $this->id, 'invoice_collection_id', 'int', false);
$this->booking_id = new object_property($this->table, $this->id, 'booking_id', 'int', false);
$this->wash_id = new object_property($this->table, $this->id, 'wash_id', 'string', false);
$this->lane = new object_property($this->table, $this->id, 'lane', 'string', false);
}
public function getCustomerByOrderId(?string $order_id): users_o
{
global $db;
$sql = "SELECT customer_id FROM orders WHERE id = $order_id";
$result = $db->query($sql);
if ($result->num_rows > 0) {
$row = $result->fetch_assoc();
return (new users_o())->getCustomerByIdOrCustomerNumber($row['customer_id']);
}
return new users_o();
}
public function getCustomerOrdersPaginated(int $customer_number, int $page = 1, int $limit = 10, array $order = ['id' => 'DESC'], string $search = null, array $filters = null): array
{
global /** @var response $response */
$db, $response;
// Add the customer number to the filters
$filters['customer_id'] = $customer_number;
// List the objects with pagination
$array = $this->listObjectsWithPagination($page, $limit, $search, $filters, $order);
// Get the total number of objects
$total = $this->getTotalObjects($search, $filters);
$response->paginate($page, $limit, $total);
return $array;
}
public function getDepartmentByOrderId($order_id): array
{
return (new departments_o())->getDepartmentById($this->department_id->value(), true);
}
public function delete(): void
{
// Set the deleted_at property to the current timestamp
$this->deleted_at->set(date('Y-m-d H:i:s'));
$this->objectChanged();
// Save the object
}
/**
* @throws Exception If the order is not selected
* This function is called when the order object is changed.
*/
public function objectChanged(): void
{
self::requireSelected();
// Reset the cached object
self::deleteCached('asArray', $this->id);
// Inform the order collection that the order has changed
$this->getOrderCollection()->objectChanged();
}
/**
* Get the order collection for the order
* @return collected_order_invoices_o The order collection
* @throws Exception If the order is not selected
*/
public function getOrderCollection(): collected_order_invoices_o
{
self::requireSelected();
$order_collection = new collected_order_invoices_o();
$order_collection->select((int)$this->invoice_collection_id->value());
$order_collection->requireSelected();
return $order_collection;
}
public function restore(): void
{
// Set the deleted_at property to null
$this->deleted_at->set(null);
// Save the object
}
/**
* @param string $plate The vehicle plate
* @param int $entries The number of last entries to return (default 10)
* @return array The orders for the vehicle plate
*/
public function getOrderHistoryByVehiclePlate(string $plate, int $entries = 10): array
{
global $db;
$sql = "SELECT * FROM $this->table WHERE reg_1 = '$plate' OR reg_2 = '$plate' OR reg_3 = '$plate' ORDER BY id DESC LIMIT $entries";
$result = $db->query($sql);
return $db->fetch_all($result);
}
public function unlinkOrdersFromInvoiceDraft(int $draftInvoiceNumber): void
{
$this->economic_module_orders->unlinkAllOrdersFromDraft($draftInvoiceNumber);
}
public function getOrderCustomer(int $orderId): users_o
{
$order = new orders_o();
$order->getOrderById($orderId);
return (new users_o())->getUserByCustomerNumber($order->customer_id->value());
}
public function getOrderById(int $id): orders_o
{
global $db;
// Get the record from the database
$sql = "SELECT * FROM $this->table WHERE id = $id";
$result = $db->query($sql);
if ($result->num_rows > 0) {
$this->id = $id;
$this->getObjectProperties();
}
return $this;
}
/**
* This function is used when an order is updated
* It takes the request data and updates the order accordingly.
* @return void
* @example {"id":410,"field":"reg_1","value":"REG12"}
*/
public function updateRequest(): void
{
global $response;
// If the permission check is not skipped, require the user to be logged in
$data = json_decode(file_get_contents('php://input'), true);
$this->id = $data['id'];
$this->getObjectProperties();
// Throw an error if the order does not exist
if (!$this->exists()) {
$response->error('Order not found, or already deleted', 400);
}
// Validate the field value
if (!isset($data['field']) || !isset($data['value'])) {
$response->error('Field and value are required', 400);
}
// Check if the value is null
if ($data['value'] === 'null' || $data['value'] === 'NULL') {
$this->{$data['field']}->nullify();
}
$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
* @throws Exception If the order is already completed
*/
public function markAsCompleted(): void
{
global /** @var db $db */
$db;
self::requireSelected();
// Check if the order is already completed
if ($this->completed_at->value() !== null) {
throw new Exception('Order is already completed');
}
// Set the completed_at property to the current timestamp
$this->completed_at->set(date('Y-m-d H:i:s'));
$sql = "UPDATE $this->table SET completed_at = '" . $this->completed_at->value() . "' WHERE id = " . $this->id;
$db->query($sql);
$this->objectChanged();
}
/**
* Get the order by invoice id
* @param int $invoiceId
* @return $this
* @throws Exception
*/
public function getOrderByInvoiceId(int $invoiceId): orders_o
{
global $db;
$sql = "SELECT id FROM economic_module_orders WHERE invoice_id = $invoiceId";
$result = $db->query($sql);
if ($result->num_rows > 0) {
$row = $result->fetch_assoc();
return $this->getOrderById($row['id']);
}
throw new Exception('Order not found');
}
/**
* Set the stripe invoicing for an order
* @throws Exception
*/
public function setStripeInvoicing(string $invoice_id, string $stripe_customer_id, string $url): void
{
self::requireSelected();
$this->stripe_module_orders->add($this->id, $invoice_id, $stripe_customer_id, $url);
self::objectChanged();
}
public function add(int $customer_id, int $cashier_id, string $reference, string $notes, int $department_id, string $reg_1 = '', string $reg_2 = '', string $reg_3 = ''): orders_o
{
global $db, $response;
try {
// Avoid SQL injection
$reference = $db->escape_string($reference);
$notes = $db->escape_string($notes);
$reg_1 = $db->escape_string($reg_1);
$reg_2 = $db->escape_string($reg_2);
$reg_3 = $db->escape_string($reg_3);
// Create a new record in the database
$sql = "INSERT INTO $this->table (customer_id, cashier_id, reference, notes, department_id, reg_1, reg_2, reg_3) VALUES ($customer_id, $cashier_id, '$reference', '$notes', $department_id, '$reg_1', '$reg_2', '$reg_3')";
$db->query($sql);
// Get the id of the new record
$this->id = $db->insert_id();
// Set the values of the object properties
$this->getObjectProperties();
self::requireSelected();
self::assignToInvoiceCollection();
return $this;
} catch (Exception $e) {
$response->error($e->getMessage());
}
}
/**
* Assign the order to an invoice collection
* @param int|null $invoiceCollectionId The invoice collection id, if not set, the default invoice collection id will be used.
* @throws Exception If the order is not selected
*/
public function assignToInvoiceCollection(int $invoiceCollectionId = null): void
{
// If the invoice collection id is not set, get the default invoice collection id
self::requireSelected();
// Get the customer
$customer = new users_o();
$customer->getUserByCustomerNumber($this->customer_id->value());
$customer->requireSelected();
// Get the invoice collection id
$invoiceCollectionId = $invoiceCollectionId ?? $customer->getNewOrderInvoiceCollectionId();
// Assign the order to the invoice collection
$this->invoice_collection_id->set($invoiceCollectionId);
self::objectChanged();
}
/**
* Get the recommended order items, based on the order history, vehicle plate and department
* @return array The recommended order items
* @throws Exception If the order is not selected
*/
public function getRecommendedOrder(): array
{
self::requireSelected();
$recommended = [
'reg_1' => self::getRecommendedOrderPlate($this->reg_1->value()),
'reg_2' => [],
'reg_3' => [],
];
return $recommended;
}
/**
* @throws Exception If the order is not selected
*/
public function getRecommendedOrderPlate(string $plate): array
{
self::requireSelected();
$result = [
'order_history' => self::get_vehicle_last_orders_items($plate),
];
// If the MotorApi is enabled, get the recommended order items based on the vehicle plate
$MotorApi = new motorapi();
if ($MotorApi->config->enabled->isTrue()) {
$MotorApi_data = $MotorApi->getRecommendedProducts($plate);
if ($MotorApi_data) {
// Get the recommended order items based on the vehicle plate
$result['motorapi'] = $MotorApi_data;
}
}
return $result;
}
/**
* Get the last orders item ids for a vehicle plate
* @param string $plate The vehicle plate
* @return array The last order item ids
*/
public function get_vehicle_last_orders_items(string $plate): array
{
global /** @var db $db */
$db;
$sql = "SELECT id FROM $this->table WHERE reg_1 = '$plate' OR reg_2 = '$plate' OR reg_3 = '$plate' AND deleted_at IS NULL ORDER BY id DESC LIMIT 5";
$result = $db->query($sql);
$orders = $db->fetch_all($result);
$order_items = [
1 => [],
2 => [],
3 => [],
4 => [],
5 => [],
];
// Get the order item (ids) from the last 5 orders
$index = 0;
foreach ( $orders as $order ) {
$index++;
$order_tmp = new orders_o();
// Get the order items for the order
$items_tmp = $order_tmp->getOrderItems($order['id']);
// Add the items to the result
foreach ( $items_tmp as $item ) {
$order_items[$index][] = [
'product_id' => $item['product_id'],
'reference' => $item['reference'],
'quantity' => $item['quantity'],
'product' => $item['product'],
];
}
}
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
* @return array The order history
*/
public function get_vehicle_order_history(string $plate): array
{
global $db;
$sql = "SELECT * FROM $this->table WHERE reg_1 = '$plate' OR reg_2 = '$plate' OR reg_3 = '$plate' AND deleted_at IS NULL ORDER BY id DESC LIMIT 5";
$result = $db->query($sql);
return $db->fetch_all($result);
}
/**
* @throws Exception If the order is not selected
*/
public function getIncludeInInvoiceCount(): int
{
self::requireSelected();
$order_items = new order_items_o();
$items = $order_items->getAllItemsAsArray($this->id, [
'include_in_invoice',
]);
$count = 0;
foreach ( $items as $item ) {
if ($item['include_in_invoice']) {
$count++;
}
}
return $count;
}
/**
* Get the wash subscription transactions for a customer
* @throws Exception If something goes wrong
*/
public function getWashSubscriptionTransactions(int $customer_number, bool $asArray = true, ?string $dateFrom = null, ?string $dateTo = null): 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,
...(!empty($dateFrom) ? ['created_at-date_from' => $dateFrom] : []),
...(!empty($dateTo) ? ['created_at-date_to' => $dateTo] : []),
],
[
'id' => 'DESC',
],
function ($object) use ($asArray) {
$res = (new orders_o())->select($object['id']);
if ($asArray) {
return $res->asArray();
}
return $res;
}
);
}
/**
* @return array The order as an array
* @throws Exception
* Convert the order object to an array
*/
public function asArray(bool $skipCache = false): array
{
self::requireSelected();
if (!$skipCache) {
// Check if the object is cached
$cached = self::getCached('asArray', $this->id);
if ($cached) {
return (array)$cached;
}
}
$tmp = [
'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(),
'wash_id' => $this->wash_id->value(),
'lane' => $this->lane->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,
];
// Cache the object
self::cache('asArray', $tmp, $this->id);
self::setCachedExpiration('asArray', self::$asArrayCacheExpiration, $this->id);
return $tmp;
}
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, [
'include_in_invoice',
'price',
'quantity',
]);
// 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;
}
/**
* Get the net amount of the orders
* @param int[] $order_ids An array of order IDs to get the net amount for
* @return array An array with the net amount for each order [ order_id => net_amount ]
* @throws Exception If the order is not selected
*/
public function getNetAmountForOrders(array $order_ids): array
{
$order_items = new order_items_o();
$tmp = $order_items->getFieldsWhere(
[
'order_id' => $order_ids,
'include_in_invoice' => true,
],
[
'price',
'quantity',
'include_in_invoice',
'order_id'
]
);
$net_amounts = [];
foreach ( $tmp as $item ) {
$net_amounts[$item['order_id']] = (float)(($net_amounts[$item['order_id']] ?? 0) + ((int)$item['price'] * (int)$item['quantity']));
}
return $net_amounts;
}
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), (int)$this->department_id->value()));
}
/** customer */
if ($response->getRequestParameter('includeCustomer') || $includeEverything) {
$customer = new users_o();
$response->add_include('customer', $customer->getUserByCustomerNumber((int)$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 isPlateSeenBefore(string $reg_1): bool
{
// Check if the plate has been seen before
$count = self::getFieldsWhere([
'reg_1' => $reg_1,
'deleted_at' => null,
], [
'reg_1',
]);
return (bool)$count;
}
public function getFixedPricingTransactions(int $customer_number, false $asArray, string|null $dateFrom = null, string|null $dateTo = null): array
{
// Get the fixed pricing transactions for a customer
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,
...(!empty($dateFrom) ? ['created_at-date_from' => $dateFrom] : []),
...(!empty($dateTo) ? ['created_at-date_to' => $dateTo] : []),
],
[
'id' => 'DESC',
],
function ($object) use ($asArray) {
$res = (new orders_o())->select($object['id']);
if ($asArray) {
return $res->asArray();
}
return $res;
}
);
}
/**
* @throws Exception
*/
public function getTankCleaningTransactions(int $customer_number, false $asArray, string $dateFrom, string $dateTo): array
{
// Get the tank cleaning transactions for a customer
return self::listObjectsWithPagination(
1,
100,
null,
[
'customer_id' => $customer_number,
'deleted_at' => null,
...(!empty($dateFrom) ? ['created_at-date_from' => $dateFrom] : []),
...(!empty($dateTo) ? ['created_at-date_to' => $dateTo] : []),
],
[
'id' => 'DESC',
],
function ($object) use ($asArray) {
$res = (new orders_o())->select($object['id']);
if ($asArray) {
return $res->asArray();
}
return $res;
}
);
}
/**
* @throws Exception
*/
public function isBooked(): bool
{
$this->requireSelected();
// Check the invoice collection has been booked
if ((int)$this->invoice_collection_id->value() > 0) {
$invoice_collection = (new collected_order_invoices_o())->select((int)$this->invoice_collection_id->value());
self::requireSelected();
return $invoice_collection->isBooked();
} else {
// If there is no invoice collection, the order is not booked
return false;
}
}
/**
* Select an order by its wash ID
* @param int|string|null $WashId The wash ID to select the order by
* @return orders_o|null The selected order object or null if no order is found
* @throws Exception
*/
public function selectByWashId(int|string|null $WashId): orders_o|null
{
$result = self::getFieldsWhere([
'wash_id' => $WashId,
'deleted_at' => null,
], [
'id'
]);
if (empty($result)) {
return null; // No order found with the given wash ID
}
$this->select((int)$result[0]['id']);
return $this;
}
/**
* Select an order by its registration number and date range (This is used to find potential duplicate orders)
* @param int|string|null $RegistrationNumber The registration number to select the order by
* @param string $dateFrom The start date of the date range (inclusive) "Y-m-d H:i:s" format
* @param string $dateTo The end date of the date range (inclusive) "Y-m-d H:i:s" format
* @return orders_o|null The selected order object or null if no order is found
* @throws Exception If the select fails
* @see xlvask_usage_log::getPotentialOrder()
*/
public function selectByRegistrationNumberAndDateRange(int|string|null $RegistrationNumber, string $dateFrom, string $dateTo): orders_o|null
{
global $db;
// Validate the date range
if (strtotime($dateFrom) === false || strtotime($dateTo) === false) {
throw new Exception('Invalid date range provided');
}
if (strtotime($dateFrom) > strtotime($dateTo)) {
throw new Exception('The start date cannot be after the end date');
}
// Ensure the registration number is a string
$RegistrationNumber = (string)$RegistrationNumber;
if (empty($RegistrationNumber)) {
throw new Exception('Registration number cannot be empty');
}
// Prepare the SQL query to find the order by registration number and date range
$RegistrationNumber = $db->escape_string($RegistrationNumber);
$dateFrom = $db->escape_string($dateFrom);
$dateTo = $db->escape_string($dateTo);
// Select the order by registration number and date range
$sql = "SELECT id FROM $this->table WHERE (reg_1 = '$RegistrationNumber' OR reg_2 = '$RegistrationNumber' OR reg_3 = '$RegistrationNumber') AND created_at BETWEEN '$dateFrom' AND '$dateTo' AND deleted_at IS NULL";
$result = $db->query($sql);
if ($result->num_rows > 0) {
$row = $result->fetch_assoc();
return $this->select((int)$row['id']);
}
return null; // No order found with the given registration number and date range
}
/**
* Add a new XL Vask order
* @param users_o $user The user who is placing the order (Billing customer)
* @param xlvask_usage_log $xlvask_usage_log The XL Vask usage log containing the wash items, department, and registration number
* @return orders_o The created order object
* @throws Exception If the order is not selected, or if the user or department is not valid
*/
public function addXLVaskOrder(users_o $user, xlvask_usage_log $xlvask_usage_log): orders_o
{
// Add a new order for XL Vask
$this->add(
(int)$user->customer_number->value(),
2285,
(string)(new customer_vehicles_o())->getPlateReferenceIfExists($xlvask_usage_log->RegistrationNumber),
'', // No notes for XL Vask orders
(int)$xlvask_usage_log->getDepartment()->id,
$xlvask_usage_log->RegistrationNumber,
);
// Set the wash ID for the order
$this->wash_id->set($xlvask_usage_log->WashId);
// Set the lane used for the order (if applicable)
$this->lane->set($xlvask_usage_log->getLane());
// Set the timestamp for the order creation to the time the wash was performed
$this->created_at->set($xlvask_usage_log->getTimestampStart());
// Add the products to the order
$firstItemId = null;
/** @var xlvask_wash_item $washItem */
foreach ( $xlvask_usage_log->WashItems as $washItem ) {
// Check if the item should be included in the order
// Check if the product is with id 64 (irrelevant product, a bi product of the wash)
if (!$washItem->shouldIncludeInOrder() || $washItem->getProduct($xlvask_usage_log)->id === 64) {
continue; // Skip items that should not be included in the order
}
$order_item = new order_items_o();
$order_item->add(
$this->id,
$washItem->getProduct($xlvask_usage_log)->id,
'',
'', //$washItem->OriginalProductName,
2285,
(int)((float)$washItem->getUnitPriceExVat() * (float)$washItem->Count),
(int)1,
$firstItemId === null ? null : $firstItemId, // Set the first item as the parent item (if applicable)
);
// Set the first item ID for the next item to link to (provided this is the first item)
if ($firstItemId === null) {
$firstItemId = (int)$order_item->id;
}
}
// Validate the order matches the desired total
$total = $this->getNetAmount();
if ($total !== $xlvask_usage_log->getTotalPrice()) {
echo "Order ID: " . $this->id . "\n";
echo "Customer: " . $user->customer_number->value() . "\n";
echo "Registration Number: " . $xlvask_usage_log->RegistrationNumber . "\n";
echo "Wash Items: \n";
foreach ( $xlvask_usage_log->WashItems as $item ) {
echo "- " . $item->getProduct($xlvask_usage_log)->name . " (Count: " . $item->Count . ", Price: " . $item->getUnitPriceExVat() . ")\n";
}
echo "Expected Total Price: " . $xlvask_usage_log->getTotalPrice() . "\n";
echo "Actual Total Price: $total\n";
echo "Order Items: \n";
foreach ( $this->getOrderItems($this->id) as $order_item ) {
echo "- " . $order_item['product']['name'] . " (Count: " . $order_item['quantity'] . ", Price: " . $order_item['product']['price'] . ")\n";
}
echo "Total: $total, Expected: " . $xlvask_usage_log->getTotalPrice() . "\n";
throw new Exception('The total amount of the order does not match the expected total. Expected: ' . $xlvask_usage_log->getTotalPrice() . ', Actual: ' . $total);
}
// Save the order
$this->objectChanged();
// Return the order object
return $this;
}
/**
* Get a list of customers who have placed orders within a specific date range
* @param string $dateFrom (E.g. "2023-01-01 00:00:00")
* @param string $dateTo (E.g. "2023-01-31 23:59:59")
* @return users_o[]
* @throws Exception
*/
public function getCustomersWithOrdersInDateRange(string $dateFrom, string $dateTo): array
{
global $db;
// Validate the date range
if (strtotime($dateFrom) === false || strtotime($dateTo) === false) {
throw new Exception('Invalid date range provided');
}
if (strtotime($dateFrom) > strtotime($dateTo)) {
throw new Exception('The start date cannot be after the end date');
}
// Prepare the SQL query to find customers with orders in the date range (unique customer IDs)
$dateFrom = $db->escape_string($dateFrom);
$dateTo = $db->escape_string($dateTo);
$sql = "SELECT DISTINCT customer_id FROM $this->table WHERE created_at BETWEEN '$dateFrom' AND '$dateTo' AND deleted_at IS NULL";
$result = $db->query($sql);
if ($result->num_rows === 0) {
return []; // No customers found in the date range
}
// Fetch all
$result = $db->fetch_all($result);
// Get the customers by their customer numbers
return (new users_o())->getUsersByCustomerNumbers(
array_map(function ($row) {
return (int)$row['customer_id'];
}, $result)
);
}
/**
* Get transactions for a specific customer within a date range
* @param int $customerNumber The customer number to filter by
* @param string $dateFrom The start date of the date range (inclusive) "Y-m-d H:i:s" format
* @param string $dateTo The end date of the date range (inclusive) "Y-m-d H:i:s" format
* @return orders_o[] The transactions for the customer within the specified date range
* @throws Exception If the date range is invalid or if no transactions are found
*/
public function getTransactionsForCustomer(int $customerNumber, string $dateFrom, string $dateTo): array
{
global $db;
// Validate the date range
if (strtotime($dateFrom) === false || strtotime($dateTo) === false) {
throw new Exception('Invalid date range provided');
}
if (strtotime($dateFrom) > strtotime($dateTo)) {
throw new Exception('The start date cannot be after the end date');
}
// Prepare the SQL query to find transactions for the customer in the date range
$dateFrom = $db->escape_string($dateFrom);
$dateTo = $db->escape_string($dateTo);
$sql = "SELECT id FROM $this->table WHERE customer_id = $customerNumber AND created_at BETWEEN '$dateFrom' AND '$dateTo' AND deleted_at IS NULL";
$result = $db->query($sql);
if ($result->num_rows === 0) {
return []; // No transactions found for the customer in the date range
}
$transactions = [];
while ($row = $result->fetch_assoc()) {
$order = new orders_o();
$order->select((int)$row['id']);
$transactions[] = $order;
}
return $transactions;
}
/**
* Get transactions for customers in a date range
* @param int[] $customers An array of customer numbers to filter by
* @param string $dateFrom The start date of the date range (inclusive) "Y-m-d H:i:s" format
* @param string $dateTo The end date of the date range (inclusive) "Y-m-d H:i:s" format
* @return array[customer_number => orders_o[]] The transactions for each customer within the specified date range
*/
public function getTransactionsForCustomersInDateRange(array $customers, string $dateFrom, string $dateTo): array
{
global $db;
// Validate the date range
if (strtotime($dateFrom) === false || strtotime($dateTo) === false) {
throw new Exception('Invalid date range provided');
}
if (strtotime($dateFrom) > strtotime($dateTo)) {
throw new Exception('The start date cannot be after the end date');
}
// Prepare the SQL query to find transactions for the customers in the date range
$dateFrom = $db->escape_string($dateFrom);
$dateTo = $db->escape_string($dateTo);
$customerNumbers = implode(',', array_map('intval', $customers));
$sql = "SELECT id, customer_id FROM $this->table WHERE customer_id IN ($customerNumbers) AND created_at BETWEEN '$dateFrom' AND '$dateTo' AND deleted_at IS NULL";
$result = $db->query($sql);
if ($result->num_rows === 0) {
return []; // No transactions found for the customers in the date range
}
$transactions = [];
while ($row = $result->fetch_assoc()) {
$order = new orders_o();
$order->select((int)$row['id']);
$transactions[(int)$row['customer_id']][] = $order;
}
return $transactions;
}
/**
* Get orders with possible duplicates in a date range
* @param string $dateFrom The start date of the date range (inclusive) "Y-m-d H:i:s" format
* @param string $dateTo The end date of the date range (inclusive) "Y-m-d H:i:s" format
* @return array An array of possible duplicate orders, keyed by registration number.
* @throws Exception
*/
public function getOrdersWithPossibleDuplicates(string $dateFrom, string $dateTo): array
{
global $db;
// Validate the date range
if (strtotime($dateFrom) === false || strtotime($dateTo) === false) {
throw new Exception('Invalid date range provided');
}
if (strtotime($dateFrom) > strtotime($dateTo)) {
throw new Exception('The start date cannot be after the end date');
}
// Prepare the SQL query to find orders with possible duplicates in the date range
$dateFrom = $db->escape_string($dateFrom);
$dateTo = $db->escape_string($dateTo);
$sql = "SELECT id, customer_id, reg_1, reg_2, reg_3, created_at FROM $this->table WHERE created_at BETWEEN '$dateFrom' AND '$dateTo' AND deleted_at IS NULL";
$result = $db->query($sql);
if ($result->num_rows === 0) {
return []; // No orders found in the date range
}
$orders = [];
while ($row = $result->fetch_assoc()) {
$tmp = [
'id' => (int)$row['id'],
'reg_1' => (string)$row['reg_1'],
'reg_2' => (string)$row['reg_2'] ?? '',
'reg_3' => (string)$row['reg_3'] ?? '',
'created_at' => (string)$row['created_at'],
];
// Add the order to the list
$orders[$tmp['reg_1']][] = [
'id' => $tmp['id'],
'reg_1' => $tmp['reg_1'],
'reg_2' => (string)$row['reg_2'] ?? '',
'reg_3' => (string)$row['reg_3'] ?? '',
'created_at' => $tmp['created_at'],
'object' => (new orders_o())->select((int)$tmp['id'])
];
}
// Filter out orders with more than one entry for the same registration numbers (in a 24 hour period)
$possibleDuplicates = [];
// Loop through the registration numbers
foreach ( $orders as $reg_1 => $orderList ) {
// If there are more than one order for the same registration number, add it to the possible duplicates
if (count($orderList) > 1) {
// Loop through the orders and check if they are within 24 hours of each other
$filteredOrders = [];
foreach ( $orderList as $order ) {
// Check if the order is within 24 hours of the previous order (if any)
if (empty($filteredOrders)) {
$filteredOrders[] = $order; // Add the first order
} else {
// Check if the order is within 24 hours of the previous order
$firstOrderTime = strtotime($filteredOrders[0]['created_at']);
$currentOrderTime = strtotime($order['created_at']);
if ($currentOrderTime - $firstOrderTime <= 86400) { // 86400 seconds = 24 hours
$filteredOrders[] = $order; // Add the order to the filtered list
}
}
}
// If there are more than one order in the filtered list, add it to the possible duplicates
if (count($filteredOrders) > 1) {
$possibleDuplicates[$reg_1] = $filteredOrders;
}
}
}
// Return the possible duplicates
return $possibleDuplicates;
}
public function setTemporaryNetAmount(float $amount): void
{
// Set a temporary net amount for the order
$this->temporary_net_amount = $amount;
}
}