feat(testing): 增加质检报告独立编辑与详情页面,不再使用弹窗

这个提交包含在:
wikiboss
2026-11-02 14:19:14 +08:00
父节点 6f877be41a
当前提交 43da3cc0ee
共有 38 个文件被更改,包括 2371 次插入 和 301 次删除

查看文件

@@ -0,0 +1,66 @@
<?php
// CLI only. First run schema, then publish files, then run with "menu".
if (PHP_SAPI !== 'cli') exit;
error_reporting(E_ALL & ~E_DEPRECATED & ~E_NOTICE);
chdir('/www/wwwroot/grain.gynhc.com');
define('APP_PATH', getcwd() . '/application/');
require getcwd() . '/thinkphp/base.php';
\think\App::initCommon();
$prefix = config('database.prefix');
if (!preg_match('/^[a-zA-Z0-9_]+$/D', $prefix)) throw new Exception('Invalid prefix');
$backup = $argv[1];
if (!is_dir($backup)) throw new Exception('Backup directory required');
if (($argv[2] ?? '') !== 'menu') {
$sql = str_replace('`fa_', '`' . $prefix, file_get_contents(__DIR__ . '/20261028_grain_forecast.sql'));
foreach (preg_split('/;\s*(?:\r?\n|$)/', trim($sql)) as $statement) {
if (trim($statement)) \think\Db::execute($statement);
}
echo "FORECAST_SCHEMA_READY\n";
exit;
}
$rules = \think\Db::name('auth_rule')->select();
$groups = \think\Db::name('auth_group')->select();
$snapshot = $backup . '/forecast-auth-backup.json';
if (!file_exists($snapshot)) {
file_put_contents($snapshot, json_encode(['rules' => $rules, 'groups' => $groups], JSON_UNESCAPED_UNICODE));
chmod($snapshot, 0600);
}
\think\Db::transaction(function () use ($rules, $groups) {
$testing = \think\Db::name('auth_rule')->where('name', 'testing')->find();
$nodes = [
'forecast' => ['温湿度预测与实测', 1],
'forecast/index' => ['查看记录', 0],
'forecast/detail' => ['查看对比', 0],
'forecast/add' => ['录入温湿度并预测', 0],
'forecast/retry' => ['重新生成预测', 0],
'forecast/actual' => ['补录真实监测值', 0],
];
$ids = [];
foreach ($nodes as $name => $info) {
$id = \think\Db::name('auth_rule')->where('name', $name)->value('id');
if (!$id) {
$id = \think\Db::name('auth_rule')->insertGetId([
'name' => $name, 'title' => $info[0], 'ismenu' => $info[1], 'type' => 'file',
'pid' => $info[1] ? ($testing ? $testing['pid'] : 0) : $ids['forecast'],
'icon' => $info[1] ? 'fa fa-line-chart' : 'fa fa-circle-o', 'menutype' => 'addtabs',
'weigh' => $info[1] && $testing ? $testing['weigh'] - 1 : 0, 'status' => 'normal',
'createtime' => time(), 'updatetime' => time(),
]);
}
$ids[$name] = $id;
}
$oldIds = [];
foreach ($rules as $rule) $oldIds[$rule['name']] = (string)$rule['id'];
foreach ($groups as $group) {
if ($group['rules'] === '*') continue;
$granted = explode(',', $group['rules']);
$canRead = false;
foreach (['testing', 'testing/index'] as $name) if (isset($oldIds[$name]) && in_array($oldIds[$name], $granted, true)) $canRead = true;
$canWrite = isset($oldIds['testing/add']) && in_array($oldIds['testing/add'], $granted, true);
if (!$canRead && !$canWrite) continue;
$extra = [$ids['forecast'], $ids['forecast/index'], $ids['forecast/detail']];
if ($canWrite) $extra = array_merge($extra, [$ids['forecast/add'], $ids['forecast/retry'], $ids['forecast/actual']]);
\think\Db::name('auth_group')->where('id', $group['id'])->update(['rules' => implode(',', array_unique(array_merge($granted, $extra)))]);
}
});
echo "FORECAST_MENU_READY\n";

查看文件

@@ -0,0 +1,35 @@
-- Replace fa_ with the configured database prefix when executing manually.
CREATE TABLE IF NOT EXISTS `fa_grain_forecast` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`gid` int unsigned NOT NULL,
`granary_name` varchar(100) NOT NULL,
`storage` varchar(255) NOT NULL DEFAULT '',
`grain_type` varchar(10) NOT NULL,
`observed_date` date NOT NULL,
`tm_i` decimal(8,4) NOT NULL,
`tm_o` decimal(8,4) NOT NULL,
`hm_i` decimal(8,4) NOT NULL,
`hm_o` decimal(8,4) NOT NULL,
`request_key` varchar(64) NOT NULL,
`status` varchar(12) NOT NULL DEFAULT 'pending',
`error` varchar(255) NOT NULL DEFAULT '',
`attempt_time` int unsigned NOT NULL DEFAULT 0,
`admin_id` int unsigned NOT NULL DEFAULT 0,
`create_time` int unsigned NOT NULL,
`update_time` int unsigned NOT NULL,
PRIMARY KEY (`id`), UNIQUE KEY `request_key` (`request_key`), KEY `gid_id` (`gid`,`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='人工温湿度及预测批次';
CREATE TABLE IF NOT EXISTS `fa_grain_forecast_point` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`forecast_id` int unsigned NOT NULL,
`horizon` tinyint unsigned NOT NULL,
`target_date` date NOT NULL,
`predicted` text NOT NULL,
`actual` text NOT NULL,
`actual_date` date DEFAULT NULL,
`actual_admin_id` int unsigned NOT NULL DEFAULT 0,
`version` int unsigned NOT NULL DEFAULT 0,
`update_time` int unsigned NOT NULL,
PRIMARY KEY (`id`), UNIQUE KEY `forecast_horizon` (`forecast_id`,`horizon`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='预测目标日期与人工实测对比';

查看文件

@@ -0,0 +1,48 @@
<?php
// CLI: php this-file.php /absolute/backup/directory
if (PHP_SAPI !== 'cli') exit;
chdir(dirname(__DIR__));
define('APP_PATH', getcwd() . '/application/');
require getcwd() . '/thinkphp/base.php';
\think\App::initCommon();
$backup = isset($argv[1]) ? $argv[1] : '';
if (!is_dir($backup)) throw new Exception('Backup directory required');
$prefix = config('database.prefix');
if (!preg_match('/^[a-zA-Z0-9_]+$/D', $prefix)) throw new Exception('Invalid prefix');
$rows = \think\Db::name('granary')->select();
$mapping = [];
$keys = [];
foreach ($rows as $row) {
$site = isset($row['site']) && in_array($row['site'], ['center', 'xishui'], true) ? $row['site'] : '';
if ($site === '') {
if (strpos($row['storage'], '习水') !== false) $site = 'xishui';
elseif (strpos($row['storage'], '中心库') !== false) $site = 'center';
else throw new Exception('Unknown site for granary ' . $row['id']);
}
$key = $site . ':' . $row['num'];
if (isset($keys[$key])) throw new Exception('Duplicate site/number: ' . $key);
$keys[$key] = true;
$mapping[$row['id']] = $site;
}
$snapshot = $backup . '/granary-sites-before.json';
if (!file_exists($snapshot)) {
file_put_contents($snapshot, json_encode(['granaries' => $rows,
'admin_scopes' => \think\Db::name('admin')->field('id,username')->select()], JSON_UNESCAPED_UNICODE));
chmod($snapshot, 0600);
}
$columns = array_column(\think\Db::query('SHOW COLUMNS FROM `' . $prefix . 'granary`'), 'Field');
if (!in_array('site', $columns)) \think\Db::execute('ALTER TABLE `' . $prefix . 'granary` ADD `site` varchar(16) NOT NULL DEFAULT \'\' COMMENT \'库区:center中心库,xishui习水库\' AFTER `storage`');
// Use bound SQL because this process may have cached the pre-migration fields.
foreach ($mapping as $id => $site) \think\Db::execute('UPDATE `' . $prefix . 'granary` SET site = ? WHERE id = ?', [$site, $id]);
$indexes = array_column(\think\Db::query('SHOW INDEX FROM `' . $prefix . 'granary`'), 'Key_name');
if (!in_array('uq_granary_site_num', $indexes)) \think\Db::execute('ALTER TABLE `' . $prefix . 'granary` ADD UNIQUE KEY `uq_granary_site_num` (`site`,`num`)');
$columns = array_column(\think\Db::query('SHOW COLUMNS FROM `' . $prefix . 'admin`'), 'Field');
if (!in_array('granary_sites', $columns)) \think\Db::execute('ALTER TABLE `' . $prefix . 'admin` ADD `granary_sites` varchar(16) NOT NULL DEFAULT \'all\' COMMENT \'库区权限:all/center/xishui/none\'');
foreach (['granary', 'admin'] as $table) {
$schema = RUNTIME_PATH . 'schema/' . config('database.database') . '.' . $prefix . $table . '.php';
if (is_file($schema)) {
copy($schema, $backup . '/' . $table . '-schema-before.php');
unlink($schema);
}
}
echo "GRANARY_SITES_READY\n";

查看文件

@@ -0,0 +1,50 @@
<?php
// CLI only: php file.php /absolute/backup/directory fixture.json
if (PHP_SAPI !== 'cli') exit;
chdir(dirname(__DIR__));
define('APP_PATH', getcwd() . '/application/');
require getcwd() . '/thinkphp/base.php';
\think\App::initCommon();
$backup = $argv[1];
if (!is_dir($backup)) throw new Exception('Backup directory required');
$fixture = json_decode(file_get_contents($argv[2]), true);
if (!is_array($fixture) || count($fixture) !== 11) throw new Exception('Expected 11 Xishui warehouses');
$prefix = config('database.prefix');
if (!preg_match('/^[a-zA-Z0-9_]+$/D', $prefix)) throw new Exception('Invalid prefix');
$ids = array_column($fixture, 'gid');
$rows = \think\Db::name('granary')->where('site', 'xishui')->where('id', 'in', $ids)->select();
if (count($rows) !== 11) throw new Exception('Warehouse ownership mismatch');
$snapshot = $backup . '/xishui-before-demo.json';
if (!file_exists($snapshot)) {
file_put_contents($snapshot, json_encode(['granaries'=>$rows,
'tests'=>\think\Db::name('testing')->where('gid','in',$ids)->select(),
'params'=>\think\Db::name('testing_params')->where('gid','in',$ids)->select()], JSON_UNESCAPED_UNICODE));
chmod($snapshot, 0600);
}
$columns = array_column(\think\Db::query('SHOW COLUMNS FROM `' . $prefix . 'testing`'), 'Field');
if (!in_array('is_demo', $columns)) \think\Db::execute('ALTER TABLE `' . $prefix . 'testing` ADD `is_demo` tinyint unsigned NOT NULL DEFAULT 0 COMMENT \'1为演示化验记录,非实测\'');
$schema = RUNTIME_PATH . 'schema/' . config('database.database') . '.' . $prefix . 'testing.php';
if (is_file($schema)) { copy($schema, $backup . '/testing-schema-before.php'); unlink($schema); }
\think\Db::clear();
$created = [];
\think\Db::transaction(function () use ($fixture, &$created, $prefix) {
foreach ($fixture as $warehouse) {
foreach ($warehouse['records'] as $record) {
$month = str_replace('-', '', $record['date']);
// Any existing record for this period is preserved, real or demo.
if (\think\Db::name('testing')->where('gid', $warehouse['gid'])->where('type', $warehouse['type'])->where('test_date', $month)->count()) continue;
if (\app\common\library\QualityRule::validate($record['params']) !== '') throw new Exception('Invalid demo parameters');
$id = (new \app\common\model\TestingModel())->add([
'gid'=>$warehouse['gid'], 'type'=>$warehouse['type'], 'season'=>2,
'sort'=>0, 'create_time'=>time(), 'update_time'=>time(),
], $record['params'], $record['date']);
// This CLI process may have cached the schema before ADD COLUMN.
\think\Db::execute('UPDATE `' . $prefix . 'testing` SET is_demo = 1 WHERE id = ?', [$id]);
$created[] = $id;
}
}
});
$manifest = $backup . '/xishui-demo-created-ids.json';
$previous = is_file($manifest) ? json_decode(file_get_contents($manifest), true) : [];
file_put_contents($manifest, json_encode(array_values(array_unique(array_merge(is_array($previous) ? $previous : [], $created)))));
echo json_encode(['result'=>'XISHUI_DEMO_READY','created_tests'=>count($created),'created_ids'=>$created], JSON_UNESCAPED_UNICODE) . "\n";