268 lines
10 KiB
PHP
268 lines
10 KiB
PHP
<?php
|
|
|
|
use classes\db;
|
|
use classes\department_wash_count_service;
|
|
|
|
function department_wash_count_integration_db(): db
|
|
{
|
|
if (!integration_enabled()) {
|
|
test()->markTestSkipped('Set RUN_INTEGRATION_TESTS=1 to run DB integration tests.');
|
|
}
|
|
|
|
$host = getenv('CONFIG_DB_HOST') ?: null;
|
|
$user = getenv('CONFIG_DB_USER') ?: null;
|
|
$password = getenv('CONFIG_DB_PASSWORD') ?: '';
|
|
$database = getenv('CONFIG_DB_DATABASE') ?: null;
|
|
if (!$host || !$user || !$database) {
|
|
test()->markTestSkipped('Missing DB env vars: CONFIG_DB_HOST/CONFIG_DB_USER/CONFIG_DB_DATABASE.');
|
|
}
|
|
|
|
app_require('classes/db.php');
|
|
app_require('classes/department_wash_count_service.php');
|
|
|
|
$db = new db([
|
|
'host' => $host,
|
|
'user' => $user,
|
|
'password' => $password,
|
|
'database' => $database,
|
|
'port' => (int)(getenv('CONFIG_DB_PORT') ?: 3306),
|
|
]);
|
|
$db->connect();
|
|
$GLOBALS['db'] = $db;
|
|
|
|
department_wash_count_prepare_tables($db);
|
|
|
|
return $db;
|
|
}
|
|
|
|
function department_wash_count_prepare_tables(db $db): void
|
|
{
|
|
$db->query(
|
|
'CREATE TABLE IF NOT EXISTS department_lanes (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
department_id INT NULL,
|
|
name VARCHAR(255) NULL,
|
|
dynamic_image_id INT NULL,
|
|
selfserve_enabled TINYINT(1) NOT NULL DEFAULT 0
|
|
)'
|
|
);
|
|
|
|
$db->query(
|
|
'CREATE TABLE IF NOT EXISTS department_selfserve_conditions (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
department_id INT NULL,
|
|
name VARCHAR(255) NULL,
|
|
product INT NULL,
|
|
action VARCHAR(64) NULL,
|
|
order_index INT NOT NULL DEFAULT 0,
|
|
deleted_at DATETIME NULL
|
|
)'
|
|
);
|
|
|
|
$db->query(
|
|
'CREATE TABLE IF NOT EXISTS department_selfserve_tasks (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
condition_id INT NULL,
|
|
product INT NULL,
|
|
description TEXT NULL,
|
|
task VARCHAR(64) NULL,
|
|
buttons TEXT NULL,
|
|
order_index INT NOT NULL DEFAULT 0,
|
|
deleted_at DATETIME NULL
|
|
)'
|
|
);
|
|
|
|
$db->query(
|
|
'CREATE TABLE IF NOT EXISTS products (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
name VARCHAR(255) NOT NULL,
|
|
description TEXT NULL,
|
|
price INT NOT NULL DEFAULT 0,
|
|
is_wash TINYINT(1) NOT NULL DEFAULT 0,
|
|
created_at DATETIME NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
)'
|
|
);
|
|
|
|
$db->query(
|
|
'CREATE TABLE IF NOT EXISTS orders (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
customer_id INT NOT NULL,
|
|
cashier_id INT NOT NULL,
|
|
reference VARCHAR(255) NULL,
|
|
notes TEXT NULL,
|
|
department_id INT NOT NULL,
|
|
reg_1 VARCHAR(64) NULL,
|
|
reg_2 VARCHAR(64) NULL,
|
|
reg_3 VARCHAR(64) NULL,
|
|
created_at DATETIME NOT NULL,
|
|
include_in_invoice TINYINT(1) NULL,
|
|
wash_id VARCHAR(255) NULL,
|
|
deleted_at DATETIME NULL
|
|
)'
|
|
);
|
|
|
|
$db->query(
|
|
'CREATE TABLE IF NOT EXISTS order_items (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
order_id INT NOT NULL,
|
|
product_id INT NOT NULL,
|
|
reference VARCHAR(255) NULL,
|
|
notes TEXT NULL,
|
|
cashier_id INT NOT NULL,
|
|
price DECIMAL(10,2) NOT NULL DEFAULT 0,
|
|
quantity INT NOT NULL DEFAULT 1,
|
|
deleted_at DATETIME NULL
|
|
)'
|
|
);
|
|
|
|
$db->query(
|
|
'CREATE TABLE IF NOT EXISTS selfserve_wash_sessions (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
lane_id INT NOT NULL,
|
|
department_id INT NOT NULL,
|
|
reg VARCHAR(64) NOT NULL,
|
|
status VARCHAR(64) NOT NULL,
|
|
allowed TINYINT(1) NOT NULL DEFAULT 0,
|
|
wash_started_at DATETIME NULL,
|
|
machine_start_triggered_at DATETIME NULL,
|
|
order_id INT NULL,
|
|
completed_at DATETIME NULL,
|
|
created_at DATETIME NULL,
|
|
deleted_at DATETIME NULL
|
|
)'
|
|
);
|
|
}
|
|
|
|
function department_wash_count_insert_product(db $db, string $suffix, bool $isWash): int
|
|
{
|
|
$productNameColumn = null;
|
|
if ($db->num_rows($db->query("SHOW COLUMNS FROM products LIKE 'name'")) > 0) {
|
|
$productNameColumn = 'name';
|
|
} elseif ($db->num_rows($db->query("SHOW COLUMNS FROM products LIKE 'title'")) > 0) {
|
|
$productNameColumn = 'title';
|
|
} else {
|
|
test()->markTestSkipped('Products table is missing both name and title columns required by this integration test.');
|
|
}
|
|
|
|
$escapedName = $db->escape_string('Wash count product ' . $suffix . ' ' . ($isWash ? 'wash' : 'minute'));
|
|
$descriptionSql = $db->num_rows($db->query("SHOW COLUMNS FROM products LIKE 'description'")) > 0
|
|
? ', description'
|
|
: '';
|
|
$descriptionValueSql = $descriptionSql !== '' ? ", 'Integration wash count product'" : '';
|
|
|
|
$db->query(
|
|
"INSERT INTO products ($productNameColumn$descriptionSql, is_wash)
|
|
VALUES ('$escapedName'$descriptionValueSql, " . ($isWash ? '1' : '0') . ')'
|
|
);
|
|
|
|
return (int)$db->insert_id();
|
|
}
|
|
|
|
function department_wash_count_insert_order(db $db, int $departmentId, int $productId, string $reference, string $createdAt, int $price = 100): int
|
|
{
|
|
$escapedReference = $db->escape_string($reference);
|
|
$escapedCreatedAt = $db->escape_string($createdAt);
|
|
|
|
$db->query(
|
|
"INSERT INTO orders (customer_id, cashier_id, reference, notes, department_id, reg_1, reg_2, reg_3, created_at)
|
|
VALUES (1, 1, '$escapedReference', 'Integration test', $departmentId, 'COUNT$departmentId', '', '', '$escapedCreatedAt')"
|
|
);
|
|
$orderId = (int)$db->insert_id();
|
|
|
|
$db->query(
|
|
"INSERT INTO order_items (order_id, product_id, reference, notes, cashier_id, price, quantity)
|
|
VALUES ($orderId, $productId, '$escapedReference', 'Integration test', 1, $price, 1)"
|
|
);
|
|
|
|
return $orderId;
|
|
}
|
|
|
|
function department_wash_count_insert_session(
|
|
db $db,
|
|
int $departmentId,
|
|
string $reg,
|
|
string $status,
|
|
?string $completedAt,
|
|
?int $orderId = null
|
|
): int {
|
|
$escapedReg = $db->escape_string($reg);
|
|
$escapedStatus = $db->escape_string($status);
|
|
$completedAtSql = $completedAt === null ? 'NULL' : "'" . $db->escape_string($completedAt) . "'";
|
|
$orderIdSql = $orderId === null ? 'NULL' : (string)$orderId;
|
|
|
|
$db->query(
|
|
"INSERT INTO selfserve_wash_sessions (lane_id, department_id, reg, status, allowed, wash_started_at, machine_start_triggered_at, order_id, completed_at, created_at)
|
|
VALUES (1, $departmentId, '$escapedReg', '$escapedStatus', 1, NULL, NULL, $orderIdSql, $completedAtSql, $completedAtSql)"
|
|
);
|
|
|
|
return (int)$db->insert_id();
|
|
}
|
|
|
|
it('counts completed self-serve minute billing sessions site-wide and dedupes linked wash orders', function (): void {
|
|
$db = department_wash_count_integration_db();
|
|
$service = new department_wash_count_service();
|
|
$suffix = (string)random_int(10000, 99999);
|
|
$departmentId = 700000 + (int)$suffix;
|
|
$orderIds = [];
|
|
$productIds = [];
|
|
$sessionIds = [];
|
|
|
|
try {
|
|
$washProductId = department_wash_count_insert_product($db, $suffix, true);
|
|
$minuteProductId = department_wash_count_insert_product($db, $suffix, false);
|
|
$productIds = [$washProductId, $minuteProductId];
|
|
|
|
$orderIds[] = department_wash_count_insert_order($db, $departmentId, $washProductId, 'plain-wash-' . $suffix, '2026-04-01 10:15:00', 100);
|
|
$linkedMinuteOrderId = department_wash_count_insert_order($db, $departmentId, $minuteProductId, 'linked-minute-' . $suffix, '2026-04-01 11:10:00', 25);
|
|
$orderIds[] = $linkedMinuteOrderId;
|
|
$linkedWashOrderId = department_wash_count_insert_order($db, $departmentId, $washProductId, 'linked-wash-' . $suffix, '2026-04-01 13:05:00', 100);
|
|
$orderIds[] = $linkedWashOrderId;
|
|
$orderIds[] = department_wash_count_insert_order($db, $departmentId, $minuteProductId, 'plain-minute-' . $suffix, '2026-04-01 14:00:00', 25);
|
|
|
|
$sessionIds[] = department_wash_count_insert_session($db, $departmentId, 'LINKMIN' . $suffix, 'COMPLETED', '2026-04-01 12:00:00', $linkedMinuteOrderId);
|
|
$sessionIds[] = department_wash_count_insert_session($db, $departmentId, 'STAND' . $suffix, 'COMPLETED', '2026-04-01 12:20:00');
|
|
$sessionIds[] = department_wash_count_insert_session($db, $departmentId, 'LINKWASH' . $suffix, 'COMPLETED', '2026-04-01 13:30:00', $linkedWashOrderId);
|
|
$sessionIds[] = department_wash_count_insert_session($db, $departmentId, 'FORCE' . $suffix, 'FORCE_STOPPED', '2026-04-01 15:00:00');
|
|
$sessionIds[] = department_wash_count_insert_session($db, $departmentId, 'OPEN' . $suffix, 'COMPLETED', null);
|
|
|
|
expect($service->countInDateRange('2026-04-01 00:00:00', '2026-04-01 23:59:59', $departmentId))->toBe(4);
|
|
|
|
$hourRows = $service->countByHourForDepartments('2026-04-01 00:00:00', '2026-04-01 23:59:59', [$departmentId]);
|
|
$countsByHour = [];
|
|
foreach ($hourRows as $row) {
|
|
$countsByHour[$row['hour_bucket']] = $row['wash_count'];
|
|
}
|
|
|
|
expect($countsByHour)->toMatchArray([
|
|
'2026-04-01 10:00:00' => 1,
|
|
'2026-04-01 11:00:00' => 1,
|
|
'2026-04-01 12:00:00' => 1,
|
|
'2026-04-01 13:00:00' => 1,
|
|
]);
|
|
expect($countsByHour)->not->toHaveKey('2026-04-01 14:00:00')
|
|
->and($countsByHour)->not->toHaveKey('2026-04-01 15:00:00');
|
|
|
|
$summary = $service->transactionSummary('2026-04-01 00:00:00', '2026-04-01 23:59:59', [$departmentId]);
|
|
expect($summary)->toMatchArray([
|
|
'quantity' => 4,
|
|
'products' => 4,
|
|
'earnings' => 250,
|
|
'washes' => 4,
|
|
]);
|
|
} finally {
|
|
if ($sessionIds !== []) {
|
|
$db->query('DELETE FROM selfserve_wash_sessions WHERE id IN (' . implode(',', array_map('intval', $sessionIds)) . ')');
|
|
}
|
|
if ($orderIds !== []) {
|
|
$orderIdsSql = implode(',', array_map('intval', $orderIds));
|
|
$db->query("DELETE FROM order_items WHERE order_id IN ($orderIdsSql)");
|
|
$db->query("DELETE FROM orders WHERE id IN ($orderIdsSql)");
|
|
}
|
|
if ($productIds !== []) {
|
|
$db->query('DELETE FROM products WHERE id IN (' . implode(',', array_map('intval', $productIds)) . ')');
|
|
}
|
|
$db->close();
|
|
}
|
|
});
|