CaiWuXiTong/modules/EcommerceFinance/Services/GoodsProfitAfsLookupSchema.php
renshen 31de7272e3 feat: 订单利润支持支付发货时间筛选 (#983)
订单利润支持支付时间、发货时间、订单时间筛选;先分页订单键再汇总当前页,补充日期索引与性能优化。
2026-11-07 16:21:24 +08:00

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";
}
}