Files
api/services/nginx/app/classes/orders_schema_bootstrap.php
T
2026-08-13 15:55:58 +00:00

196 lines
6.6 KiB
PHP

<?php
namespace classes;
/**
* Ensures additive schema for orders metadata.
*/
class orders_schema_bootstrap
{
private static bool $initialized = false;
public static function ensureTables(): void
{
if (self::$initialized) {
return;
}
global $db;
if (!isset($db) || !is_object($db) || !method_exists($db, 'query')) {
return;
}
if (!self::tableExists($db, 'orders')) {
return;
}
if (!self::columnExists($db, 'orders', 'include_in_invoice')) {
$db->query(
"ALTER TABLE orders
ADD COLUMN include_in_invoice TINYINT(1) NULL DEFAULT NULL
AFTER created_at"
);
}
if (!self::columnExists($db, 'orders', 'safety_seal')) {
$db->query(
"ALTER TABLE orders
ADD COLUMN safety_seal VARCHAR(255) NULL DEFAULT NULL
AFTER po"
);
}
if (self::tableExists($db, 'order_items')) {
if (!self::columnExists($db, 'order_items', 'reason_code')) {
$db->query(
"ALTER TABLE order_items
ADD COLUMN reason_code VARCHAR(64) NULL DEFAULT NULL
AFTER include_in_invoice"
);
}
if (!self::columnExists($db, 'order_items', 'reason_label_snapshot')) {
$db->query(
"ALTER TABLE order_items
ADD COLUMN reason_label_snapshot VARCHAR(255) NULL DEFAULT NULL
AFTER reason_code"
);
}
if (!self::columnExists($db, 'order_items', 'reason_comment')) {
$db->query(
"ALTER TABLE order_items
ADD COLUMN reason_comment TEXT NULL
AFTER reason_label_snapshot"
);
}
if (!self::columnExists($db, 'order_items', 'extra_sale_reason_code')) {
$db->query(
"ALTER TABLE order_items
ADD COLUMN extra_sale_reason_code VARCHAR(64) NULL DEFAULT NULL
AFTER notes"
);
}
if (!self::columnExists($db, 'order_items', 'extra_sale_comment')) {
$db->query(
"ALTER TABLE order_items
ADD COLUMN extra_sale_comment TEXT NULL
AFTER extra_sale_reason_code"
);
}
}
self::backfillBookingPoDefaults($db);
self::ensureIndex($db, 'orders', 'idx_orders_period_customer_created_deleted', 'customer_id, created_at, deleted_at');
self::ensureIndex($db, 'orders', 'idx_orders_period_created_deleted_customer', 'created_at, deleted_at, customer_id');
self::ensureIndex($db, 'order_items', 'idx_order_items_order_deleted', 'order_id, deleted_at');
self::ensureIndex($db, 'order_items', 'idx_order_items_reason_code', 'reason_code');
self::ensureIndex($db, 'customer_attributes', 'idx_customer_attributes_attribute_user', 'attribute, user_id');
self::$initialized = true;
}
private static function backfillBookingPoDefaults(object $db): void
{
if (
!self::tableExists($db, 'order_bookings')
|| !self::columnExists($db, 'orders', 'booking_id')
|| !self::columnExists($db, 'orders', 'po')
|| !self::columnExists($db, 'order_bookings', 'po')
|| !self::columnExists($db, 'order_bookings', 'customer_number')
|| !self::columnExists($db, 'order_bookings', 'department')
|| !self::columnExists($db, 'order_bookings', 'deleted_at')
) {
return;
}
$db->query(
"UPDATE orders o
INNER JOIN order_bookings b ON b.id = o.booking_id
AND b.customer_number = o.customer_id
AND b.department = o.department_id
AND b.deleted_at IS NULL
SET o.po = b.po
WHERE o.booking_id IS NOT NULL
AND o.booking_id > 0
AND (o.po IS NULL OR TRIM(o.po) = '')
AND b.po IS NOT NULL
AND TRIM(b.po) <> ''"
);
}
private static function tableExists(object $db, string $table): bool
{
$table = self::escapeIdentifier($table);
$result = $db->query("SHOW TABLES LIKE '{$table}'");
if ($result === false || !is_object($result) || !property_exists($result, 'num_rows')) {
return false;
}
return (int)$result->num_rows > 0;
}
private static function columnExists(object $db, string $table, string $column): bool
{
$table = self::escapeIdentifier($table);
$column = self::escapeIdentifier($column);
$result = $db->query("SHOW COLUMNS FROM `{$table}` LIKE '{$column}'");
if ($result === false || !is_object($result) || !property_exists($result, 'num_rows')) {
return false;
}
return (int)$result->num_rows > 0;
}
private static function ensureIndex(object $db, string $table, string $index, string $columns): void
{
if (
!self::tableExists($db, $table)
|| self::indexExists($db, $table, $index)
|| !self::columnsExist($db, $table, $columns)
) {
return;
}
$table = self::escapeIdentifier($table);
$index = self::escapeIdentifier($index);
$db->query("ALTER TABLE `{$table}` ADD INDEX `{$index}` ({$columns})");
}
private static function columnsExist(object $db, string $table, string $columns): bool
{
foreach (explode(',', $columns) as $column) {
$column = trim($column, " \t\n\r\0\x0B`");
if ($column === '' || !self::columnExists($db, $table, $column)) {
return false;
}
}
return true;
}
private static function indexExists(object $db, string $table, string $index): bool
{
$table = self::escapeIdentifier($table);
$index = self::escapeIdentifier($index);
$result = $db->query("SHOW INDEX FROM `{$table}` WHERE Key_name = '{$index}'");
if ($result === false || !is_object($result) || !property_exists($result, 'num_rows')) {
return false;
}
return (int)$result->num_rows > 0;
}
private static function escapeIdentifier(string $value): string
{
return str_replace(['\\', "'", '`'], ['\\\\', "\\'", ''], $value);
}
}