调整部分前端布局和后端增删改逻辑

This commit is contained in:
焦龙言
2026-09-26 23:11:05 +08:00
parent 977e7f996e
commit 17d991a74a
117 changed files with 311994 additions and 3192 deletions
@@ -5,12 +5,11 @@ from collections.abc import Callable
from pathlib import Path
SQL_ROOT = Path(__file__).resolve().parents[2] / 'sql'
INITIALIZERS = ('shierfeng-fastapi.sql', 'shierfeng-fastapi-pg.sql')
INITIALIZERS = ('shierfeng-fastapi.sql',)
PATCH_PATH = SQL_ROOT / 'patches/20260724_pallet_workflow_mysql.sql'
MENU_PROMOTION_PATCH_PATH = SQL_ROOT / 'patches/20260727_promote_business_menus_mysql.sql'
BUSINESS_MENU_ICON_PATCH_PATH = SQL_ROOT / 'patches/20260728_add_business_menu_icons_mysql.sql'
TEST_BATCH_MYSQL_PATCH_PATH = SQL_ROOT / 'patches/20260922_test_data_batch_mysql.sql'
TEST_BATCH_PG_PATCH_PATH = SQL_ROOT / 'patches/20260922_test_data_batch_pg.sql'
SYS_MENU_VALUE_COUNT = 20
SYS_MENU_PERMS_INDEX = 13
SYS_MENU_ICON_INDEX = 14
@@ -1007,18 +1006,6 @@ def _column_definitions(body: str) -> dict[str, str]:
return definitions
def _is_postgres_naive_timestamp_with_default_precision(definition: str) -> bool:
return (
re.fullmatch(
r'[`"]?[a-z_][a-z0-9_]*[`"]?\s+'
r'timestamp(?:\s+without\s+time\s+zone)?(?:\s+not\s+null)?',
definition.strip(),
flags=re.I,
)
is not None
)
def _assert_test_batch_sql_contract(
test_case: unittest.TestCase,
sql: str,
@@ -1064,18 +1051,9 @@ def _assert_test_batch_sql_contract(
test_case.assertNotRegex(definitions['test_batch_id'], r'\bnot\s+null\b')
index_name = f'idx_{table_name}_test_batch_id'
if dialect == 'mysql':
index_pattern = rf'\b(?:add\s+)?(?:index|key)\s+{index_name}\s*\(\s*test_batch_id\s*\)'
test_case.assertRegex(normalized, index_pattern)
test_case.assertIn("comment '测试数据批次id;null表示正式数据'", normalized)
else:
index_pattern = rf'\bcreate\s+index\s+{index_name}\s+on\s+{table_name}\s*\(\s*test_batch_id\s*\)'
test_case.assertRegex(normalized, index_pattern)
test_case.assertRegex(
normalized,
rf'comment\s+on\s+column\s+{table_name}\.test_batch_id\s+is\s+'
r"'测试数据批次id;null表示正式数据'",
)
index_pattern = rf'\b(?:add\s+)?(?:index|key)\s+{index_name}\s*\(\s*test_batch_id\s*\)'
test_case.assertRegex(normalized, index_pattern)
test_case.assertIn("comment '测试数据批次id;null表示正式数据'", normalized)
constraint_name = f'fk_{table_name}_test_batch_id'
test_case.assertRegex(
@@ -1345,9 +1323,7 @@ class SqlContractTest(unittest.TestCase):
def test_test_batch_initializers_and_patches_match_contract(self) -> None:
scripts = (
(SQL_ROOT / 'shierfeng-fastapi.sql', 'mysql', False),
(SQL_ROOT / 'shierfeng-fastapi-pg.sql', 'postgresql', False),
(TEST_BATCH_MYSQL_PATCH_PATH, 'mysql', True),
(TEST_BATCH_PG_PATCH_PATH, 'postgresql', True),
)
for path, dialect, is_patch in scripts:
with self.subTest(path=path.name):
@@ -1361,7 +1337,7 @@ class SqlContractTest(unittest.TestCase):
def test_test_batch_patches_migrate_legacy_menu_permissions_without_role_grants(self) -> None:
legacy_removed_ids = set(LEGACY_REMOVED_BUTTON_SPECS)
for path in (TEST_BATCH_MYSQL_PATCH_PATH, TEST_BATCH_PG_PATCH_PATH):
for path in (TEST_BATCH_MYSQL_PATCH_PATH,):
with self.subTest(path=path.name):
raw = path.read_text(encoding='utf-8')
statements = _sql_statements(raw)
@@ -1430,32 +1406,24 @@ class SqlContractTest(unittest.TestCase):
self.assertEqual(EXPECTED_FOREIGN_KEYS[table], foreign_keys)
self.assertEqual(EXPECTED_CHECKS[table], checks)
def test_postgres_business_datetimes_preserve_default_microsecond_precision(self) -> None:
def test_mysql_business_datetimes_use_native_datetime_columns(self) -> None:
self.assertEqual(40, sum(len(columns) for columns in EXPECTED_DATETIME_COLUMNS.values()))
tables = _create_table_bodies((SQL_ROOT / 'shierfeng-fastapi-pg.sql').read_text(encoding='utf-8'))
tables = _create_table_bodies((SQL_ROOT / 'shierfeng-fastapi.sql').read_text(encoding='utf-8'))
invalid_columns = []
for table, expected_columns in EXPECTED_DATETIME_COLUMNS.items():
definitions = _column_definitions(tables[table])
actual_columns = {
column
for column, definition in definitions.items()
if re.search(r'\btimestamp\b', definition, flags=re.I)
if re.search(r'\bdatetime\b', definition, flags=re.I)
}
self.assertEqual(expected_columns, actual_columns, table)
for column in expected_columns:
definition = definitions[column]
if not _is_postgres_naive_timestamp_with_default_precision(definition):
if not re.search(r'\bdatetime\b', definition, flags=re.I):
invalid_columns.append(f'{table}.{column}')
self.assertEqual([], sorted(invalid_columns))
def test_postgres_datetime_type_validator_rejects_precision_and_timezone_mutations(self) -> None:
self.assertTrue(_is_postgres_naive_timestamp_with_default_precision('occurred_at timestamp'))
self.assertTrue(
_is_postgres_naive_timestamp_with_default_precision('occurred_at timestamp without time zone not null')
)
self.assertFalse(_is_postgres_naive_timestamp_with_default_precision('occurred_at timestamp(3)'))
self.assertFalse(_is_postgres_naive_timestamp_with_default_precision('occurred_at timestamp with time zone'))
def test_initializers_define_fixed_business_menus_and_permissions(self) -> None:
self.assertEqual(18, len(EXPECTED_PAGE_PERMISSIONS))
self.assertEqual(43, len(EXPECTED_BUTTON_PERMISSIONS))
@@ -1679,18 +1647,6 @@ class SqlContractTest(unittest.TestCase):
with self.assertRaises(AssertionError):
_assert_business_menu_fields(self, mutated)
def test_mysql_and_postgresql_business_menu_fields_are_identical(self) -> None:
mysql_rows = _business_menu_rows((SQL_ROOT / INITIALIZERS[0]).read_text(encoding='utf-8'))
postgres_rows = _business_menu_rows((SQL_ROOT / INITIALIZERS[1]).read_text(encoding='utf-8'))
compared_indexes = tuple(range(15))
mysql_contract = {
menu_id: tuple(row[index] for index in compared_indexes) for menu_id, row in mysql_rows.items()
}
postgres_contract = {
menu_id: tuple(row[index] for index in compared_indexes) for menu_id, row in postgres_rows.items()
}
self.assertEqual(mysql_contract, postgres_contract)
def test_initializers_do_not_grant_new_business_menus_to_roles(self) -> None:
for filename in INITIALIZERS:
with self.subTest(filename=filename):
@@ -1717,14 +1673,10 @@ class SqlContractTest(unittest.TestCase):
any(int(row[0]) in REMOVED_AI_DICT_CODES or row[4] == 'ai_provider_type' for row in dict_data)
)
def test_initializers_keep_menu_allocator_and_use_native_dialects(self) -> None:
def test_initializer_uses_mysql_native_menu_allocator(self) -> None:
mysql = (SQL_ROOT / 'shierfeng-fastapi.sql').read_text(encoding='utf-8').lower()
postgres = (SQL_ROOT / 'shierfeng-fastapi-pg.sql').read_text(encoding='utf-8').lower()
self.assertRegex(mysql, r'auto_increment\s*=\s*2000')
self.assertNotRegex(mysql, r'\bbigserial\b|\balter\s+sequence\b|\bcomment\s+on\b')
self.assertRegex(postgres, r'alter\s+sequence\s+sys_menu_menu_id_seq\s+restart\s+2000')
self.assertNotIn('`', postgres)
self.assertNotRegex(postgres, r'\bauto_increment\b|\bengine\s*=')
def test_initializers_create_event_before_inventory_foreign_key(self) -> None:
for filename in INITIALIZERS: