122 lines
4.7 KiB
PHP
122 lines
4.7 KiB
PHP
<?php
|
|
|
|
namespace Modules\EcommerceFinance\Services;
|
|
|
|
use Illuminate\Database\ConnectionInterface;
|
|
use Illuminate\Support\Facades\Schema;
|
|
|
|
/** Derived lookup only: the upstream strings and financial values stay intact. */
|
|
class GoodsProfitAfsLookupSchema
|
|
{
|
|
public const TABLE = 'ops_jst_goods_profit_kpi_items_afs';
|
|
|
|
public const ORDER_DATE_COLUMN = '_mirror_afs_order_time';
|
|
|
|
public const PAY_DATE_COLUMN = '_mirror_afs_pay_time';
|
|
|
|
public const SEND_DATE_COLUMN = '_mirror_afs_send_time';
|
|
|
|
/** 页面时间字段 => 上游原列 / 只读查询用的派生日期列。 */
|
|
public const DATE_COLUMNS = [
|
|
'order_time' => [
|
|
'source' => 'undefined7',
|
|
'lookup' => self::ORDER_DATE_COLUMN,
|
|
],
|
|
'pay_time' => [
|
|
'source' => 'undefined5',
|
|
'lookup' => self::PAY_DATE_COLUMN,
|
|
],
|
|
'send_time' => [
|
|
'source' => 'undefined6',
|
|
'lookup' => self::SEND_DATE_COLUMN,
|
|
],
|
|
];
|
|
|
|
/** @deprecated Use ORDER_DATE_COLUMN. */
|
|
public const DATE_COLUMN = self::ORDER_DATE_COLUMN;
|
|
|
|
public const INDEXES = [
|
|
'idx_afs_order_time_id' => [self::ORDER_DATE_COLUMN, 'id'],
|
|
'idx_afs_shop_order_time_id' => ['undefined8', self::ORDER_DATE_COLUMN, 'id'],
|
|
'idx_afs_pay_time_id' => [self::PAY_DATE_COLUMN, 'id'],
|
|
'idx_afs_shop_pay_time_id' => ['undefined8', self::PAY_DATE_COLUMN, 'id'],
|
|
'idx_afs_send_time_id' => [self::SEND_DATE_COLUMN, 'id'],
|
|
'idx_afs_shop_send_time_id' => ['undefined8', self::SEND_DATE_COLUMN, 'id'],
|
|
];
|
|
|
|
public function ensure(ConnectionInterface $db): void
|
|
{
|
|
if (! in_array($db->getDriverName(), ['mysql', 'mariadb'], true)) {
|
|
return;
|
|
}
|
|
|
|
$schema = Schema::connection($db->getName());
|
|
if (! $schema->hasTable(self::TABLE)
|
|
|| ! $schema->hasColumn(self::TABLE, 'id')) {
|
|
return;
|
|
}
|
|
|
|
$statements = [];
|
|
foreach (self::DATE_COLUMNS as $definition) {
|
|
$source = $definition['source'];
|
|
$lookup = $definition['lookup'];
|
|
if (! $schema->hasColumn(self::TABLE, $source)
|
|
|| $schema->hasColumn(self::TABLE, $lookup)) {
|
|
continue;
|
|
}
|
|
|
|
// MariaDB requires virtual columns and their indexes in separate
|
|
// ALTER statements to allow concurrent writes with LOCK=NONE.
|
|
$statements[] = 'ADD COLUMN `' . $lookup
|
|
. '` VARCHAR(19) CHARACTER SET ascii COLLATE ascii_bin GENERATED ALWAYS AS ('
|
|
. $this->dateExpression($source) . ') VIRTUAL';
|
|
}
|
|
$indexes = [];
|
|
foreach (self::INDEXES as $name => $columns) {
|
|
if (! $schema->hasIndex(self::TABLE, $name)
|
|
&& $schema->hasColumns(self::TABLE, $columns)) {
|
|
$indexes[] = 'ADD INDEX `' . $name . '` (`' . implode('`, `', $columns) . '`)';
|
|
}
|
|
}
|
|
if ($indexes !== []) $statements[] = implode(', ', $indexes);
|
|
|
|
if ($statements !== []) {
|
|
// Fail rather than silently copying/locking this multi-million-row table.
|
|
$wait = (int) $db->selectOne('SELECT @@SESSION.lock_wait_timeout AS seconds')->seconds;
|
|
$db->statement('SET SESSION lock_wait_timeout = 5');
|
|
try {
|
|
foreach ($statements as $statement) {
|
|
$db->statement('ALTER TABLE `' . self::TABLE . '` ' . $statement . ', ALGORITHM=INPLACE, LOCK=NONE');
|
|
}
|
|
} finally {
|
|
$db->statement('SET SESSION lock_wait_timeout = ' . $wait);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function dateExpression(string $column = 'undefined7'): string
|
|
{
|
|
$source = null;
|
|
foreach (self::DATE_COLUMNS as $definition) {
|
|
if ($definition['source'] === $column) {
|
|
$source = $column;
|
|
break;
|
|
}
|
|
}
|
|
if ($source === null) {
|
|
throw new \InvalidArgumentException('订单利润日期字段不安全。');
|
|
}
|
|
|
|
$raw = "REPLACE(TRIM(`{$source}`), '/', '-')";
|
|
$pattern = '^[0-9]{1,4}-(0?[1-9]|1[0-2])-(0?[1-9]|[12][0-9]|3[01])'
|
|
. '([[:space:]]+([01]?[0-9]|2[0-3])(:[0-5]?[0-9]){0,2}([.][0-9]+)?)?$';
|
|
|
|
// Guard malformed strings so a future bad source value cannot break an
|
|
// indexed-column update in strict mode. Fractional seconds were already
|
|
// ignored by the original format. CHAR preserves its calendar semantics
|
|
// (e.g. 2026-02-31), unlike coercing the result into a DATETIME column.
|
|
return "CASE WHEN {$raw} REGEXP '{$pattern}' THEN CAST(STR_TO_DATE("
|
|
. "SUBSTRING_INDEX({$raw}, '.', 1), '%Y-%m-%d %H:%i:%s') AS CHAR(19)) ELSE NULL END";
|
|
}
|
|
}
|