Guard the wash subscription distribution query after invoice-inclusion filtering removes all candidate orders, preventing an empty IN () clause on the invoicing distribution endpoint. Verified with focused syntax, Pest, PHPStan, and invoicing unit-suite checks.
544 lines
22 KiB
PHP
544 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();
|
|
$last_order = $this->getLastOrderByPlate((string)$this->reg->value());
|
|
return $last_order?->id ? (int)$last_order->id : 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);
|
|
$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;
|
|
}
|
|
if (empty($orders)) {
|
|
return [];
|
|
}
|
|
$order_ids = implode(',', $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 ($order_ids)
|
|
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);
|
|
}
|
|
}
|