Files
api/services/nginx/app/objects/customer_vehicles_o.php
T
Jeppe Bundgaard b45e270fed Skip transactions not included in invoicing
- Updated `customer_vehicles_o` to exclude transactions not marked for invoicing during processing.
2025-12-01 13:20:07 +01:00

552 lines
22 KiB
PHP

<?php
namespace objects;
use classes\db;
use classes\object_property;
use classes\xlvask;
use Exception;
use helpers\xlvask_customer;
use helpers\xlvask_vehicle;
use helpers\xlvask_vehicle_type;
use helpers\xlvask_vehicles;
use mysqli_result;
use traits\db_object_t;
class customer_vehicles_o extends db
{
use db_object_t;
public object_property $customer_id; // The id of the customer
public object_property $type; // The type of the vehicle
public object_property $reg; // The registration number of the vehicle
public object_property $wash_subscription; // The wash subscription of the vehicle (boolean)
public object_property $notes; // The notes of the vehicle
public object_property $reference; // The reference of the vehicle
public object_property $deleted_at; // The timestamp of when the record was "deleted"
/**
* @param array $getAddons [db_object_t child object]
* @return array
*/
private static function transformObjectsToArray(array $getAddons): array
{
return array_map(function ($addon) {
return $addon->asArray();
}, $getAddons);
}
/**
* Convert the object to an array
* @return array
* @throws Exception If the object is not selected
*/
public function asArray(): array
{
self::requireSelected();
// Check if the vehicle is cached
$cached = $this->getCached('asArray', $this->id);
if ($cached) {
return (array)$cached;
}
$wash_subscription = (bool)$this->wash_subscription->value();
$addons = self::transformObjectsToArray(
self::getAddons()
);
$available_addons = self::transformObjectsToArray(
self::getAvailableAddons()
);
$last_order_id = self::getLastOrderId();
$customer_number = (int)$this->customer_id->value();
$xlvask = $this->hasXLVask() ? $this->getXLVask() : null;
$tmp = [
'id' => (int)$this->id,
'user_id' => (int)(new users_o())->getUserIdFromEconomic((int)$customer_number),
'customer_id' => (int)$this->customer_id->value(),
'customer_name' => (string)(new users_o())->getCustomerName((int)$customer_number),
'type' => (int)$this->type->value(),
'reg' => (string)$this->reg->value(),
'reference' => ($this->reference->value() ? (string)$this->reference->value() : null),
'wash_subscription' => $wash_subscription,
'barred' => (bool)(new users_o())->isCustomerBarred((int)$customer_number),
'addons' => [
'enabled' => count($addons),
'available' => count($available_addons),
'list' => $addons,
],
'last_order_id' => ($last_order_id ? (int)$last_order_id : null),
'xlvask' => ($xlvask?->toArray()),
'vehicle_types' => array_map(function ($vehicle_type) {
return $vehicle_type->toArray();
}, $this->getVehicleTypes()),
];
// Cache the result
$this->cache('asArray', $tmp, $this->id);
$this->setCachedExpiration('asArray', self::$asArrayCacheExpiration, $this->id);
return $tmp;
}
/**
* Get the addons for the vehicle
* @return array{customer_vehicles_addons_o}
* @throws Exception If the object is not selected
*/
public function getAddons(): array
{
self::requireSelected();
$addons = (new customer_vehicles_addons_o())->getFieldsWhere([
'vehicle_id' => (int)$this->id,
], [
'id',
]);
return array_map(function ($addon) {
return (new customer_vehicles_addons_o())->select((int)$addon['id']);
}, $addons);
}
/**
* Get the available addons for the vehicle
* @return array{product_options_o}
* @throws Exception If the object is not selected
*/
public function getAvailableAddons(): array
{
self::requireSelected();
$available_addons = (new product_options_o())->getProductOptions((int)$this->type->value());
// Filter the addons, to only show the ones that has "subscription_allowed" set to true
$available_addons = array_filter($available_addons, function ($addon) {
return (bool)$addon['product']['subscription_allowed'];
});
return array_map(function ($addon) {
return (new product_options_o())->select((int)$addon['id']);
}, $available_addons);
}
/**
* Get the last order id for the vehicle
* @return int|null
* @throws Exception If the object is not selected
* @throws Exception If the there is an error selecting the order
*/
private function getLastOrderId(): ?int
{
self::requireSelected();
$orders_o = (new orders_o());
$orders = $orders_o->getFieldsWhere([
'reg_1' => (string)$this->reg->value(),
'deleted_at' => null,
], [
'id',
]);
if (count($orders) > 0) {
$last_order = array_pop($orders);
return (int)$last_order['id'];
}
return null;
}
/**
* Get last order with order items for plate (supports unregistered vehicles)
* @param string $reg The registration number of the vehicle
* @return orders_o|null The last order object for the vehicle, or null if no orders found
* @throws Exception If there is an error selecting the order
*/
public function getLastOrderByPlate(string $reg): ?orders_o
{
$orders_o = (new orders_o());
$orders = $orders_o->getFieldsWhere([
'reg_1' => $reg,
'deleted_at' => null,
], [
'id',
]);
// Find the last order with order items
while (count($orders) > 0) {
$last_order = array_pop($orders);
$order = (new orders_o())->select((int)$last_order['id']);
if ($order->exists() && count((new order_items_o())->getFieldsWhere([
'order_id' => (int)$order->id,
'deleted_at' => null,
], ['id'])) > 0) {
return $order;
}
}
return null;
}
/**
* Check if the vehicle has an XLVask object
* @return bool
* @throws Exception If the object is not selected
* @note This method checks if the vehicle is registered in the XLVask system.
*/
public function hasXLVask(): bool
{
self::requireSelected();
$xlvask = new xlvask();
$vehicles = new $xlvask->helpers->xlvask_vehicles();
/** @var xlvask_vehicles $vehicles */
return $vehicles->getVehicleByRegistrationNumber((string)$this->reg->value(), true) !== null;
}
/**
* Get the XLVask object for the vehicle
* @return xlvask_vehicle
* @throws Exception If the object is not selected
*/
public function getXLVask(): xlvask_vehicle
{
self::requireSelected();
$xlvask = new xlvask();
$vehicles = new $xlvask->helpers->xlvask_vehicles();
/** @var xlvask_vehicles $vehicles */
return $vehicles->getVehicleByRegistrationNumber((string)$this->reg->value());
}
/**
* Get the vehicle types applicable to the vehicle (Wash types)
* @return array{xlvask_vehicle_type}
* @note This method retrieves the vehicle types from the XLVask system that are applicable to the vehicle.
* @return array of xlvask_vehicle_type objects
* @throws Exception If the object is not selected or if there is an error retrieving the vehicle types
* @see xlvask_vehicle_type
*/
public function getVehicleTypes(): array
{
self::requireSelected();
return (new xlvask())->helpers->xlvask_vehicle_types::getVehicleTypesByProductId(
(int)$this->type->value()
);
}
/**
* Add the default addons to the vehicle
* @return void
* @throws Exception If the object is not selected
* @throws Exception If the there is an error adding the addon
* @throws Exception If the there is an error selecting the addon
* @throws Exception If the there is an error selecting the vehicle
*/
public function addDefaultAddons(): void
{
self::requireSelected();
// If any of the available addons have the product id in the $automatic_addon_product_ids array, add it to the vehicle
$automatic_addon_product_ids = [
23, // Spot Free- Varevogn
24, // Spot Free- Lastbil
];
$available_addons = self::getAvailableAddons();
/** @var product_options_o $addon */
foreach ( $available_addons as $addon ) {
//echo 'Vehicle id: ' . $this->id . PHP_EOL;
//echo 'Addon id: ' . $addon->id . PHP_EOL;
//echo 'Primary product id: ' . $addon->product_id->value() . PHP_EOL;
//echo 'Addon product id: ' . $addon->option_id->value() . PHP_EOL;
//echo 'Is in array: ' . (in_array((int)$addon->option_id->value(), $automatic_addon_product_ids) ? 'true' : 'false') . PHP_EOL;
//print_r($available_addons);
if (in_array((int)$addon->option_id->value(), $automatic_addon_product_ids)) {
// Add the addon to the vehicle
(new customer_vehicles_addons_o())->add(
(int)$this->id,
(int)$addon->id,
(int)1,
);
}
}
}
/**
* @throws Exception If the creation of the object fails
*/
public function add(int $customer_number, int $type, string $reg, bool $subscription, string|null $reference = null): void
{
global $db, $response;
$tmp_id = self::add_object([
'customer_id' => (int)$customer_number,
'type' => (int)$type,
'reg' => (string)$reg,
'wash_subscription' => (bool)$subscription ? 1 : 0,
'reference' => ($reference ? (string)$reference : null),
]);
self::select((int)$tmp_id);
self::objectChanged();
self::addDefaultAddons();
}
public function objectChanged(): void
{
// Clear the cache for the object
$this->deleteCached('asArray', $this->id);
}
public function structure(): void
{
$this->setTable('customer_vehicles');
}
public function getObjectProperties(): void
{
$this->customer_id = new object_property($this->table, $this->id, 'customer_id', 'int', true);
$this->type = new object_property($this->table, $this->id, 'type', 'int', true);
$this->reg = new object_property($this->table, $this->id, 'reg', 'string', true);
$this->reference = new object_property($this->table, $this->id, 'reference', 'string', false);
$this->wash_subscription = new object_property($this->table, $this->id, 'wash_subscription', 'bool', false);
$this->deleted_at = new object_property($this->table, $this->id, 'deleted_at', 'timestamp', false);
}
/**
* Get the customer object for the vehicle
* @param string $reg The registration number of the vehicle
* @return int|null The customer number for the vehicle, or null if the vehicle is not registered to a customer
* @throws Exception If the customer_id does not exist
*/
public function getPlateCustomerNumber(string $reg): int|null
{
// Get the customer id from the vehicle
$result = self::getFieldsWhere([
'reg' => $reg,
], [
'customer_id',
]);
// If the customer id is not found, return null
if (count($result) === 0) {
return null;
}
// If the customer id is found, return it
return (int)$result[0]['customer_id'];
}
/**
* Get the plate reference if it exists, otherwise return a fallback value
* @param string $RegistrationNumber The registration number of the vehicle
* @param mixed $fallback The fallback value to return if the vehicle does not exist or has no reference
* @throws Exception
*/
public function getPlateReferenceIfExists(string $RegistrationNumber, mixed $fallback = ''): mixed
{
$vehicle = self::selectByPlate($RegistrationNumber);
if (!$vehicle->exists()) {
return $fallback; // If the vehicle does not exist, return the fallback value
}
// If the vehicle has a reference, return it
if ($vehicle->reference->value() !== null) {
return (string)$vehicle->reference->value();
}
// If the vehicle does not have a reference, return the fallback value
return $fallback;
}
/**
* @throws Exception
*/
public function selectByPlate(string $reg): customer_vehicles_o
{
$result = self::getFieldsWhere([
'reg' => $reg,
], [
'id',
]);
if (count($result) === 0) {
return new customer_vehicles_o();
}
return (new customer_vehicles_o())->select((int)$result[0]['id']);
}
/**
* Set the vehicle type id for the vehicle in the XLVask system
* @param string $vehicleTypeId The vehicle type id to set in the XLVask system
* @note This method updates the vehicle type in the XLVask system for the vehicle.
* @throws Exception If the associated customer is not registered in the XLVask system
* @throws Exception If the vehicle is not registered in the XLVask system
* @throws Exception If the vehicle type id is not valid or if there is an error updating the vehicle type in the XLVask system
* @throws Exception If the object is not selected
* @see xlvask_vehicle
* @example
* $vehicle = new customer_vehicles_o();
* $vehicle->select(1); // Select the vehicle with id 1
* $vehicleTypeId = 'some-vehicle-type-id'; // The vehicle type id to set
* $vehicle->setVehicleTypeId($vehicleTypeId);
* @see xlvask_vehicle_type
*/
public function setVehicleTypeId(string $vehicleTypeId): void
{
self::requireSelected();
$xlvask = new xlvask();
// Check if the vehicle is registered in the XLVask system
if (!$this->hasXLVask()) {
// Create a new XLVask vehicle
throw new Exception('Vehicle is not registered in the XLVask system. Please register the vehicle first.');
}
// Set the vehicle type id
$xlvask_vehicle = $this->getXLVask();
$xlvask_vehicle->vehicleTypeId = $vehicleTypeId;
// Update the XLVask vehicle
$xlvask->updateVehicle($xlvask_vehicle);
// Cache the updated vehicle
$xlvask->getCache()->setVehicleCache($xlvask_vehicle->registrationNumber, $xlvask_vehicle);
$this->objectChanged();
}
/**
* Create a new vehicle in the XLVask system
* * @param string $registrationNumber The registration number of the vehicle
* @param int $customerId The customer id of the vehicle
* @param string|null $vehicleTypeId The vehicle type id of the vehicle, if not provided, the default vehicle type will be used
* @return xlvask_vehicle The created XLVask vehicle object
* @throws Exception If the customer is not registered in the XLVask system
* @throws Exception If the vehicle is already registered in the XLVask system
* @throws Exception If the vehicle type id is not valid or if there is an error creating the vehicle in the XLVask system
* @throws Exception If the object is not selected
* @note This method creates a new vehicle in the XLVask system for the vehicle.
* @see xlvask_vehicle
* @see xlvask_vehicle_type
* @example
* $vehicle = new customer_vehicles_o();
* $vehicle->select(1); // Select the vehicle with id 1
* $vehicleTypeId = 'some-vehicle-type-id'; // The vehicle type id to set, if not provided, the default vehicle type will be used
* $vehicle->createXLVaskVehicle($vehicleTypeId);
*/
public function createXLVaskVehicle(string $vehicleTypeId): xlvask_vehicle
{
self::requireSelected();
$xlvask = new xlvask();
// Check if the customer is registered in the XLVask system
$xlvask_customer = $this->getXLVaskCustomer();
// Make sure the vehicle type id is valid for this vehicle type
$valid_vehicle_types = $xlvask->helpers->xlvask_vehicle_types::getVehicleTypesByProductId(
(int)$this->type->value()
);
if (!in_array($vehicleTypeId, array_map(fn($type) => $type->vehicleTypeId, $valid_vehicle_types))) {
throw new Exception('Invalid vehicle type id provided. Please provide a valid vehicle type id for this vehicle type.');
}
// Create a new XLVask vehicle
$xlvask_vehicle = new xlvask_vehicle();
$xlvask_vehicle->registrationNumber = (string)$this->reg->value();
$xlvask_vehicle->customerId = $xlvask_customer->customerId;
$xlvask_vehicle->vehicleTypeId = $vehicleTypeId;
$xlvask_vehicle->active = true; // Set the vehicle as active
$xlvask_vehicle->autoStartOnLpr = true; // Enable auto start on LPR
// Add the vehicle to the XLVask system
$xlvask->createVehicle($xlvask_vehicle);
// Cache the vehicle
$vehicles = $xlvask->helpers->new($xlvask->helpers->xlvask_vehicles);
/** @var xlvask_vehicles $vehicles */
$xlvask_vehicle = $vehicles::getVehicleByRegistrationNumber((string)$this->reg->value());
$xlvask->getCache()->setVehicleCache($xlvask_vehicle->registrationNumber, $xlvask_vehicle);
$this->objectChanged();
return $xlvask_vehicle;
}
/**
* Get the XLVask customer object for the vehicle
* @return xlvask_customer The XLVask customer object for the vehicle
* @throws Exception If the object is not selected
* @note This method retrieves the XLVask customer object for the vehicle.
*/
public function getXLVaskCustomer(): xlvask_customer
{
self::requireSelected();
$customer_number = (int)$this->customer_id->value();
$xlvask = new xlvask();
if ($xlvask->getCache()->isCustomerCached($customer_number)) {
return $xlvask->getCache()->getCustomerCache($customer_number);
}
// If the customer is not cached, throw an exception
throw new Exception('Customer is not registered in the XLVask system. Please register the customer first.');
}
/**
* Set the auto start on LPR for the vehicle in the XLVask system
* @param bool $autoStartOnLpr Whether to enable auto start on LPR for the vehicle
* @throws Exception If the object is not selected
* @note This method updates the auto start on LPR setting for the vehicle in the XLVask system.
*/
public function setAutoStartOnLpr(bool $autoStartOnLpr): void
{
self::requireSelected();
$xlvask_vehicle = $this->getXLVask();
$xlvask_vehicle->autoStartOnLpr = $autoStartOnLpr;
$xlvask = new xlvask();
$xlvask->updateVehicle($xlvask_vehicle);
// Cache the updated vehicle
$xlvask->getCache()->setVehicleCache($xlvask_vehicle->registrationNumber, $xlvask_vehicle);
$this->objectChanged();
}
/**
* Get the first two transactions, that are not deleted, and contains the product of the vehicle type for the vehicle in the list of transaction ids
* @param array $transaction_ids [ 1, 2, 3 ]
* @return array An array of transaction ids
* @throws Exception If the object is not selected
* @note This method retrieves the first two transactions that are not deleted and contain the product of the vehicle type for the vehicle in the provided list of transaction ids.
* The transactions are ordered by creation date in descending order.
* If no transactions are found, an empty array is returned.
* If only one transaction is found, an array with one transaction id is returned.
* If two transactions are found, an array with two transaction ids is returned.
* @example
* $vehicle = new customer_vehicles_o();
* $vehicle->select(1); // Select the vehicle with id 1
* $transaction_ids = [1, 2, 3, 4, 5]; // The list of transaction ids to check
* $result = $vehicle->getSubscriptionAppliedTransactionsFromList($transaction_ids);
* print_r($result); // Array of transaction ids
*/
public function getSubscriptionAppliedTransactionsFromList(array $transaction_ids): array
{
global $db;
self::requireSelected();
if (count($transaction_ids) === 0) {
return [];
}
//print_r($transaction_ids);
// Convert the array of transaction ids to a comma separated string
$orders = '';
foreach ($transaction_ids as $transaction_id) {
// Check if the transaction is included in the invoicing.
$tmp = (new orders_o())->select((int)$transaction_id);
if (!$tmp->isIncludedInInvoicing()) {
continue; // The transaction is not included in the invoicing, skip it
}
$orders .= (int)$transaction_id . ',';
}
// Remove the last comma
$orders = rtrim($orders, ',');
// Get the first two transactions that are not deleted and contains at least one order item with the 'product_id' of the vehicle type for the vehicle
$query = "
SELECT o.id
FROM orders o
JOIN order_items oi ON oi.order_id = o.id
WHERE o.deleted_at IS NULL
AND oi.deleted_at IS NULL
AND o.id IN ($orders)
AND oi.product_id = " . (int)$this->type->value() . "
GROUP BY o.id
ORDER BY o.created_at ASC
LIMIT 2
";
//echo $query . PHP_EOL;
/** @var mysqli_result $result */
$result = $db->query($query);
$transaction_ids = $db->fetch_all($result);
return array_map(fn($transaction) => (int)$transaction['id'], $transaction_ids);
}
/**
* @throws Exception
*/
public function getLastTransactions(int $numberOfTransactions = 5): array
{
self::requireSelected();
$orders_o = new orders_o();
$orders = $orders_o->getFieldsWhere([
'reg_1' => (string)$this->reg->value(),
'deleted_at' => null,
], [
'id',
], [
'created_at' => 'DESC',
], $numberOfTransactions);
return array_map(fn($order) => (int)$order['id'], $orders);
}
}