diff --git a/shierfeng-fastapi-backend/module_business/controller/customer_controller.py b/shierfeng-fastapi-backend/module_business/controller/customer_controller.py index b4728dd..d9b89cd 100644 --- a/shierfeng-fastapi-backend/module_business/controller/customer_controller.py +++ b/shierfeng-fastapi-backend/module_business/controller/customer_controller.py @@ -111,7 +111,7 @@ async def edit_master_customer( summary='兼容旧接口,固定拒绝,请使用测试数据批次清理', description='兼容旧接口,固定拒绝单条物理删除,请使用测试数据批次清理', response_model=ResponseBaseModel, - dependencies=[UserInterfaceAuthDependency('master:customer:remove')], + dependencies=[UserInterfaceAuthDependency('master:customer:list')], ) @Log(title='客户管理', business_type=BusinessType.DELETE) async def delete_master_customer( diff --git a/shierfeng-fastapi-backend/module_business/controller/inbound_controller.py b/shierfeng-fastapi-backend/module_business/controller/inbound_controller.py index 0920af2..d2dcdda 100644 --- a/shierfeng-fastapi-backend/module_business/controller/inbound_controller.py +++ b/shierfeng-fastapi-backend/module_business/controller/inbound_controller.py @@ -163,7 +163,7 @@ async def edit_business_inbound( summary='不支持单条物理删除,请通过测试数据批次清理', description='不支持单条物理删除,请通过测试数据批次清理', response_model=ResponseBaseModel, - dependencies=[UserInterfaceAuthDependency('business:inbound:remove')], + dependencies=[UserInterfaceAuthDependency('business:inbound:list')], ) @Log(title='仓库入库', business_type=BusinessType.DELETE) async def delete_business_inbound( diff --git a/shierfeng-fastapi-backend/module_business/controller/line_controller.py b/shierfeng-fastapi-backend/module_business/controller/line_controller.py index 74bfbd6..9c24d3f 100644 --- a/shierfeng-fastapi-backend/module_business/controller/line_controller.py +++ b/shierfeng-fastapi-backend/module_business/controller/line_controller.py @@ -109,7 +109,7 @@ async def edit_master_line( summary='兼容旧接口,固定拒绝,请使用测试数据批次清理', description='兼容旧接口,固定拒绝单条物理删除,请使用测试数据批次清理', response_model=ResponseBaseModel, - dependencies=[UserInterfaceAuthDependency('master:line:remove')], + dependencies=[UserInterfaceAuthDependency('master:line:list')], ) @Log(title='生产线管理', business_type=BusinessType.DELETE) async def delete_master_line( diff --git a/shierfeng-fastapi-backend/module_business/controller/pallet_controller.py b/shierfeng-fastapi-backend/module_business/controller/pallet_controller.py index 3e4d646..3ae4f22 100644 --- a/shierfeng-fastapi-backend/module_business/controller/pallet_controller.py +++ b/shierfeng-fastapi-backend/module_business/controller/pallet_controller.py @@ -123,7 +123,7 @@ async def edit_master_pallet( summary='兼容旧接口,固定拒绝,请使用测试数据批次清理', description='兼容旧接口,固定拒绝单条物理删除,请使用测试数据批次清理', response_model=ResponseBaseModel, - dependencies=[UserInterfaceAuthDependency('master:pallet:remove')], + dependencies=[UserInterfaceAuthDependency('master:pallet:list')], ) @Log(title='托盘管理', business_type=BusinessType.DELETE) async def delete_master_pallet( diff --git a/shierfeng-fastapi-backend/module_business/controller/shipment_controller.py b/shierfeng-fastapi-backend/module_business/controller/shipment_controller.py index e9add7a..44b46f3 100644 --- a/shierfeng-fastapi-backend/module_business/controller/shipment_controller.py +++ b/shierfeng-fastapi-backend/module_business/controller/shipment_controller.py @@ -180,7 +180,7 @@ async def edit_business_shipment( summary='不支持单条物理删除,请通过测试数据批次清理', description='不支持单条物理删除,请通过测试数据批次清理', response_model=ResponseBaseModel, - dependencies=[UserInterfaceAuthDependency('business:shipment:remove')], + dependencies=[UserInterfaceAuthDependency('business:shipment:list')], ) @Log(title='整托发货', business_type=BusinessType.DELETE) async def delete_business_shipment( diff --git a/shierfeng-fastapi-backend/module_business/controller/site_controller.py b/shierfeng-fastapi-backend/module_business/controller/site_controller.py index c51e865..2f25fe1 100644 --- a/shierfeng-fastapi-backend/module_business/controller/site_controller.py +++ b/shierfeng-fastapi-backend/module_business/controller/site_controller.py @@ -161,7 +161,7 @@ async def edit_master_site( summary='兼容旧接口,固定拒绝,请使用测试数据批次清理', description='兼容旧接口,固定拒绝单条物理删除,请使用测试数据批次清理', response_model=ResponseBaseModel, - dependencies=[UserInterfaceAuthDependency('master:site:remove')], + dependencies=[UserInterfaceAuthDependency('master:site:list')], ) @Log(title='客户现场管理', business_type=BusinessType.DELETE) async def delete_master_site( diff --git a/shierfeng-fastapi-backend/module_business/controller/strain_controller.py b/shierfeng-fastapi-backend/module_business/controller/strain_controller.py index e1cd303..e63f85d 100644 --- a/shierfeng-fastapi-backend/module_business/controller/strain_controller.py +++ b/shierfeng-fastapi-backend/module_business/controller/strain_controller.py @@ -111,7 +111,7 @@ async def edit_master_strain( summary='兼容旧接口,固定拒绝,请使用测试数据批次清理', description='兼容旧接口,固定拒绝单条物理删除,请使用测试数据批次清理', response_model=ResponseBaseModel, - dependencies=[UserInterfaceAuthDependency('master:strain:remove')], + dependencies=[UserInterfaceAuthDependency('master:strain:list')], ) @Log(title='菌种管理', business_type=BusinessType.DELETE) async def delete_master_strain( diff --git a/shierfeng-fastapi-backend/module_business/controller/test_batch_controller.py b/shierfeng-fastapi-backend/module_business/controller/test_batch_controller.py new file mode 100644 index 0000000..41243e6 --- /dev/null +++ b/shierfeng-fastapi-backend/module_business/controller/test_batch_controller.py @@ -0,0 +1,262 @@ +from typing import Annotated + +from fastapi import Path, Request, Response +from sqlalchemy.ext.asyncio import AsyncSession + +from common.annotation.log_annotation import Log, RequestLogFieldRoot +from common.aspect.db_seesion import DBSessionDependency +from common.aspect.interface_auth import UserInterfaceAuthDependency +from common.aspect.pre_auth import CurrentUserDependency, PreAuthDependency +from common.enums import BusinessType +from common.router import APIRouterPro +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_business.entity.vo.test_batch_vo import ( + AdoptionExecuteRequestModel, + AdoptionPreviewRequestModel, + PurgeExecuteRequestModel, + TestBatchCreateModel, + TestBatchEditModel, + TestBatchUserAssignModel, +) +from module_business.service.test_batch_adoption_service import TestBatchAdoptionService +from module_business.service.test_batch_purge_service import TestBatchPurgeService +from module_business.service.test_batch_service import TestBatchService +from utils.response_util import ResponseUtil + +test_batch_controller = APIRouterPro( + prefix='/system/test-batch', + tags=['系统管理-测试数据批次'], + dependencies=[PreAuthDependency()], +) + + +@test_batch_controller.get('/options') +async def list_test_batch_options( + request: Request, + query_db: Annotated[AsyncSession, DBSessionDependency()], + current_user: Annotated[CurrentUserModel, CurrentUserDependency()], +) -> Response: + result = await TestBatchService.list_available_batches(query_db, current_user) + return ResponseUtil.success(data=result) + + +@test_batch_controller.get( + '/list', + dependencies=[UserInterfaceAuthDependency('system:testbatch:list')], +) +@Log( + title='测试数据批次', + business_type=BusinessType.OTHER, + request_log_mode='summary', + response_log_mode='summary', +) +async def list_test_batches( + request: Request, + query_db: Annotated[AsyncSession, DBSessionDependency()], + current_user: Annotated[CurrentUserModel, CurrentUserDependency()], +) -> Response: + result = await TestBatchService.list_batches(query_db, current_user) + return ResponseUtil.success(rows=result, dict_content={'total': len(result)}) + + +@test_batch_controller.post( + '', + dependencies=[UserInterfaceAuthDependency('system:testbatch:add')], +) +@Log( + title='测试数据批次', + business_type=BusinessType.INSERT, + request_log_mode='summary', + response_log_mode='summary', +) +async def create_test_batch( + request: Request, + command: TestBatchCreateModel, + query_db: Annotated[AsyncSession, DBSessionDependency()], + current_user: Annotated[CurrentUserModel, CurrentUserDependency()], +) -> Response: + result = await TestBatchService.create_batch(query_db, command, current_user) + return ResponseUtil.success(msg=result.message) + + +@test_batch_controller.put( + '/{batch_id}', + dependencies=[UserInterfaceAuthDependency('system:testbatch:edit')], +) +@Log( + title='测试数据批次', + business_type=BusinessType.UPDATE, + request_log_mode='summary', + response_log_mode='summary', +) +async def edit_test_batch( + request: Request, + batch_id: Annotated[int, Path(gt=0, description='测试批次ID')], + command: TestBatchEditModel, + query_db: Annotated[AsyncSession, DBSessionDependency()], + current_user: Annotated[CurrentUserModel, CurrentUserDependency()], +) -> Response: + result = await TestBatchService.edit_batch(query_db, batch_id, command, current_user) + return ResponseUtil.success(msg=result.message) + + +@test_batch_controller.put( + '/{batch_id}/users', + dependencies=[UserInterfaceAuthDependency('system:testbatch:assign')], +) +@Log( + title='测试数据批次', + business_type=BusinessType.GRANT, + request_log_mode='summary', + response_log_mode='summary', +) +async def assign_test_batch_users( + request: Request, + batch_id: Annotated[int, Path(gt=0, description='测试批次ID')], + command: TestBatchUserAssignModel, + query_db: Annotated[AsyncSession, DBSessionDependency()], + current_user: Annotated[CurrentUserModel, CurrentUserDependency()], +) -> Response: + result = await TestBatchService.replace_users(query_db, batch_id, command, current_user) + return ResponseUtil.success(msg=result.message) + + +@test_batch_controller.put( + '/{batch_id}/close', + dependencies=[UserInterfaceAuthDependency('system:testbatch:close')], +) +@Log( + title='测试数据批次', + business_type=BusinessType.UPDATE, + request_log_mode='summary', + response_log_mode='summary', +) +async def close_test_batch( + request: Request, + batch_id: Annotated[int, Path(gt=0, description='测试批次ID')], + query_db: Annotated[AsyncSession, DBSessionDependency()], + current_user: Annotated[CurrentUserModel, CurrentUserDependency()], +) -> Response: + result = await TestBatchService.close_batch(query_db, batch_id, current_user) + return ResponseUtil.success(msg=result.message) + + +@test_batch_controller.put( + '/{batch_id}/reopen', + dependencies=[UserInterfaceAuthDependency('system:testbatch:close')], +) +@Log( + title='测试数据批次', + business_type=BusinessType.UPDATE, + request_log_mode='summary', + response_log_mode='summary', +) +async def reopen_test_batch( + request: Request, + batch_id: Annotated[int, Path(gt=0, description='测试批次ID')], + query_db: Annotated[AsyncSession, DBSessionDependency()], + current_user: Annotated[CurrentUserModel, CurrentUserDependency()], +) -> Response: + result = await TestBatchService.reopen_batch(query_db, batch_id, current_user) + return ResponseUtil.success(msg=result.message) + + +@Log( + title='测试数据批次', + business_type=BusinessType.CLEAN, + request_log_mode='include', + request_include_fields=(RequestLogFieldRoot.PATH_PARAMS.field('batch_id'),), + response_log_mode='summary', +) +@test_batch_controller.post( + '/{batch_id}/adoption/preview', + dependencies=[UserInterfaceAuthDependency('system:testbatch:adopt')], +) +async def preview_test_batch_adoption( + request: Request, + batch_id: Annotated[int, Path(gt=0, description='测试批次ID')], + command: AdoptionPreviewRequestModel, + query_db: Annotated[AsyncSession, DBSessionDependency()], + current_user: Annotated[CurrentUserModel, CurrentUserDependency()], +) -> Response: + result = await TestBatchAdoptionService.preview(query_db, batch_id, command, current_user) + return ResponseUtil.success(data=result) + + +@Log( + title='测试数据批次', + business_type=BusinessType.CLEAN, + request_log_mode='include', + request_include_fields=(RequestLogFieldRoot.PATH_PARAMS.field('batch_id'),), + response_log_mode='summary', +) +@test_batch_controller.post( + '/{batch_id}/adoption/execute', + dependencies=[UserInterfaceAuthDependency('system:testbatch:adopt')], +) +async def execute_test_batch_adoption( + request: Request, + batch_id: Annotated[int, Path(gt=0, description='测试批次ID')], + command: AdoptionExecuteRequestModel, + query_db: Annotated[AsyncSession, DBSessionDependency()], + current_user: Annotated[CurrentUserModel, CurrentUserDependency()], +) -> Response: + result = await TestBatchAdoptionService.execute(query_db, batch_id, command, current_user) + return ResponseUtil.success(msg=result.message) + + +@Log( + title='测试数据批次', + business_type=BusinessType.CLEAN, + request_log_mode='include', + request_include_fields=(RequestLogFieldRoot.PATH_PARAMS.field('batch_id'),), + response_log_mode='summary', +) +@test_batch_controller.post( + '/{batch_id}/purge/preview', + dependencies=[UserInterfaceAuthDependency('system:testbatch:purge')], +) +async def preview_test_batch_purge( + request: Request, + batch_id: Annotated[int, Path(gt=0, description='测试批次ID')], + query_db: Annotated[AsyncSession, DBSessionDependency()], + current_user: Annotated[CurrentUserModel, CurrentUserDependency()], +) -> Response: + result = await TestBatchPurgeService.preview(query_db, batch_id, current_user) + return ResponseUtil.success(data=result) + + +@Log( + title='测试数据批次', + business_type=BusinessType.CLEAN, + request_log_mode='include', + request_include_fields=(RequestLogFieldRoot.PATH_PARAMS.field('batch_id'),), + response_log_mode='summary', +) +@test_batch_controller.post( + '/{batch_id}/purge', + dependencies=[UserInterfaceAuthDependency('system:testbatch:purge')], +) +async def purge_test_batch( + request: Request, + batch_id: Annotated[int, Path(gt=0, description='测试批次ID')], + command: PurgeExecuteRequestModel, + query_db: Annotated[AsyncSession, DBSessionDependency()], + current_user: Annotated[CurrentUserModel, CurrentUserDependency()], +) -> Response: + result = await TestBatchPurgeService.purge(query_db, batch_id, command, current_user) + return ResponseUtil.success(msg=result.message) + + +@test_batch_controller.get( + '/{batch_id}', + dependencies=[UserInterfaceAuthDependency('system:testbatch:query')], +) +async def get_test_batch( + request: Request, + batch_id: Annotated[int, Path(gt=0, description='测试批次ID')], + query_db: Annotated[AsyncSession, DBSessionDependency()], + current_user: Annotated[CurrentUserModel, CurrentUserDependency()], +) -> Response: + result = await TestBatchService.get_batch_detail(query_db, batch_id, current_user) + return ResponseUtil.success(data=result) diff --git a/shierfeng-fastapi-backend/module_business/controller/warehouse_controller.py b/shierfeng-fastapi-backend/module_business/controller/warehouse_controller.py index 28b28af..db62bf3 100644 --- a/shierfeng-fastapi-backend/module_business/controller/warehouse_controller.py +++ b/shierfeng-fastapi-backend/module_business/controller/warehouse_controller.py @@ -166,7 +166,7 @@ async def edit_master_warehouse( summary='兼容旧接口,固定拒绝,请使用测试数据批次清理', description='兼容旧接口,固定拒绝单条物理删除,请使用测试数据批次清理', response_model=ResponseBaseModel, - dependencies=[UserInterfaceAuthDependency('master:warehouse:remove')], + dependencies=[UserInterfaceAuthDependency('master:warehouse:list')], ) @Log(title='仓库管理', business_type=BusinessType.DELETE) async def delete_master_warehouse( diff --git a/shierfeng-fastapi-backend/sql/patches/20260922_test_data_batch_mysql.sql b/shierfeng-fastapi-backend/sql/patches/20260922_test_data_batch_mysql.sql index 6cc0184..6b325bb 100644 --- a/shierfeng-fastapi-backend/sql/patches/20260922_test_data_batch_mysql.sql +++ b/shierfeng-fastapi-backend/sql/patches/20260922_test_data_batch_mysql.sql @@ -135,3 +135,25 @@ ALTER TABLE biz_pallet_event ADD INDEX idx_biz_pallet_event_test_batch_id (test_batch_id), ADD CONSTRAINT fk_biz_pallet_event_test_batch_id FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +-- 菜单迁移:移除旧物理删除权限,加入测试数据批次管理菜单。 +-- 新菜单不自动授予普通角色;超级管理员由现有 admin 权限访问。 +START TRANSACTION; + +DELETE FROM sys_role_menu +WHERE menu_id IN (1068, 1072, 1076, 1081, 1085, 1090, 1095, 1102); + +DELETE FROM sys_menu +WHERE menu_id IN (1068, 1072, 1076, 1081, 1085, 1090, 1095, 1102); + +INSERT INTO sys_menu VALUES +(519, '测试数据批次', 1, '7', 'testBatch', 'system/testBatch/index', '', '', 1, 0, 'C', '0', '0', 'system:testbatch:list', '#', 'admin', SYSDATE(), '', NULL, '测试数据批次菜单'), +(1109, '批次查询', 519, '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:testbatch:query', '#', 'admin', SYSDATE(), '', NULL, ''), +(1110, '批次新增', 519, '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:testbatch:add', '#', 'admin', SYSDATE(), '', NULL, ''), +(1111, '批次修改', 519, '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:testbatch:edit', '#', 'admin', SYSDATE(), '', NULL, ''), +(1112, '批次授权', 519, '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:testbatch:assign', '#', 'admin', SYSDATE(), '', NULL, ''), +(1113, '批次关闭', 519, '5', '', '', '', '', 1, 0, 'F', '0', '0', 'system:testbatch:close', '#', 'admin', SYSDATE(), '', NULL, ''), +(1114, '数据纳入', 519, '6', '', '', '', '', 1, 0, 'F', '0', '0', 'system:testbatch:adopt', '#', 'admin', SYSDATE(), '', NULL, ''), +(1115, '批次清理', 519, '7', '', '', '', '', 1, 0, 'F', '0', '0', 'system:testbatch:purge', '#', 'admin', SYSDATE(), '', NULL, ''); + +COMMIT; diff --git a/shierfeng-fastapi-backend/sql/patches/20260922_test_data_batch_pg.sql b/shierfeng-fastapi-backend/sql/patches/20260922_test_data_batch_pg.sql index 36ae09b..be1ed57 100644 --- a/shierfeng-fastapi-backend/sql/patches/20260922_test_data_batch_pg.sql +++ b/shierfeng-fastapi-backend/sql/patches/20260922_test_data_batch_pg.sql @@ -137,3 +137,25 @@ COMMENT ON COLUMN biz_pallet_event.test_batch_id IS '测试数据批次ID;NULL CREATE INDEX idx_biz_pallet_event_test_batch_id ON biz_pallet_event (test_batch_id); ALTER TABLE biz_pallet_event ADD CONSTRAINT fk_biz_pallet_event_test_batch_id FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +-- 菜单迁移:移除旧物理删除权限,加入测试数据批次管理菜单。 +-- 新菜单不自动授予普通角色;超级管理员由现有 admin 权限访问。 +BEGIN; + +DELETE FROM sys_role_menu +WHERE menu_id IN (1068, 1072, 1076, 1081, 1085, 1090, 1095, 1102); + +DELETE FROM sys_menu +WHERE menu_id IN (1068, 1072, 1076, 1081, 1085, 1090, 1095, 1102); + +INSERT INTO sys_menu VALUES +(519, '测试数据批次', 1, '7', 'testBatch', 'system/testBatch/index', '', '', 1, 0, 'C', '0', '0', 'system:testbatch:list', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, '测试数据批次菜单'), +(1109, '批次查询', 519, '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:testbatch:query', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''), +(1110, '批次新增', 519, '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:testbatch:add', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''), +(1111, '批次修改', 519, '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:testbatch:edit', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''), +(1112, '批次授权', 519, '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:testbatch:assign', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''), +(1113, '批次关闭', 519, '5', '', '', '', '', 1, 0, 'F', '0', '0', 'system:testbatch:close', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''), +(1114, '数据纳入', 519, '6', '', '', '', '', 1, 0, 'F', '0', '0', 'system:testbatch:adopt', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''), +(1115, '批次清理', 519, '7', '', '', '', '', 1, 0, 'F', '0', '0', 'system:testbatch:purge', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); + +COMMIT; diff --git a/shierfeng-fastapi-backend/sql/shierfeng-fastapi-pg.sql b/shierfeng-fastapi-backend/sql/shierfeng-fastapi-pg.sql index 17afce4..69d8155 100644 --- a/shierfeng-fastapi-backend/sql/shierfeng-fastapi-pg.sql +++ b/shierfeng-fastapi-backend/sql/shierfeng-fastapi-pg.sql @@ -1575,47 +1575,47 @@ insert into sys_menu values (516, '产线消耗', 123, '4', 'consumption', 'report/consumption/index', '', '', 1, 0, 'C', '0', '0', 'report:consumption:list', '#', 'admin', current_timestamp, '', null, ''), (517, '退回仓库', 123, '5', 'reportReturn', 'report/return/index', '', '', 1, 0, 'C', '0', '0', 'report:return:list', '#', 'admin', current_timestamp, '', null, ''), (518, '托盘追溯', 123, '6', 'palletTrace', 'report/palletTrace/index', '', '', 1, 0, 'C', '0', '0', 'report:pallettrace:list', '#', 'admin', current_timestamp, '', null, ''), +(519, '测试数据批次', 1, '7', 'testBatch', 'system/testBatch/index', '', '', 1, 0, 'C', '0', '0', 'system:testbatch:list', '#', 'admin', current_timestamp, '', null, ''), (1065, '菌种查询', 502, '1', '', '', '', '', 1, 0, 'F', '0', '0', 'master:strain:query', '#', 'admin', current_timestamp, '', null, ''), (1066, '菌种新增', 502, '2', '', '', '', '', 1, 0, 'F', '0', '0', 'master:strain:add', '#', 'admin', current_timestamp, '', null, ''), (1067, '菌种修改', 502, '3', '', '', '', '', 1, 0, 'F', '0', '0', 'master:strain:edit', '#', 'admin', current_timestamp, '', null, ''), -(1068, '菌种删除', 502, '4', '', '', '', '', 1, 0, 'F', '0', '0', 'master:strain:remove', '#', 'admin', current_timestamp, '', null, ''), (1069, '客户查询', 503, '1', '', '', '', '', 1, 0, 'F', '0', '0', 'master:customer:query', '#', 'admin', current_timestamp, '', null, ''), (1070, '客户新增', 503, '2', '', '', '', '', 1, 0, 'F', '0', '0', 'master:customer:add', '#', 'admin', current_timestamp, '', null, ''), (1071, '客户修改', 503, '3', '', '', '', '', 1, 0, 'F', '0', '0', 'master:customer:edit', '#', 'admin', current_timestamp, '', null, ''), -(1072, '客户删除', 503, '4', '', '', '', '', 1, 0, 'F', '0', '0', 'master:customer:remove', '#', 'admin', current_timestamp, '', null, ''), (1073, '现场查询', 504, '1', '', '', '', '', 1, 0, 'F', '0', '0', 'master:site:query', '#', 'admin', current_timestamp, '', null, ''), (1074, '现场新增', 504, '2', '', '', '', '', 1, 0, 'F', '0', '0', 'master:site:add', '#', 'admin', current_timestamp, '', null, ''), (1075, '现场修改', 504, '3', '', '', '', '', 1, 0, 'F', '0', '0', 'master:site:edit', '#', 'admin', current_timestamp, '', null, ''), -(1076, '现场删除', 504, '4', '', '', '', '', 1, 0, 'F', '0', '0', 'master:site:remove', '#', 'admin', current_timestamp, '', null, ''), (1077, '分配用户', 504, '5', '', '', '', '', 1, 0, 'F', '0', '0', 'master:site:assign', '#', 'admin', current_timestamp, '', null, ''), (1078, '产线查询', 505, '1', '', '', '', '', 1, 0, 'F', '0', '0', 'master:line:query', '#', 'admin', current_timestamp, '', null, ''), (1079, '产线新增', 505, '2', '', '', '', '', 1, 0, 'F', '0', '0', 'master:line:add', '#', 'admin', current_timestamp, '', null, ''), (1080, '产线修改', 505, '3', '', '', '', '', 1, 0, 'F', '0', '0', 'master:line:edit', '#', 'admin', current_timestamp, '', null, ''), -(1081, '产线删除', 505, '4', '', '', '', '', 1, 0, 'F', '0', '0', 'master:line:remove', '#', 'admin', current_timestamp, '', null, ''), (1082, '仓库查询', 506, '1', '', '', '', '', 1, 0, 'F', '0', '0', 'master:warehouse:query', '#', 'admin', current_timestamp, '', null, ''), (1083, '仓库新增', 506, '2', '', '', '', '', 1, 0, 'F', '0', '0', 'master:warehouse:add', '#', 'admin', current_timestamp, '', null, ''), (1084, '仓库修改', 506, '3', '', '', '', '', 1, 0, 'F', '0', '0', 'master:warehouse:edit', '#', 'admin', current_timestamp, '', null, ''), -(1085, '仓库删除', 506, '4', '', '', '', '', 1, 0, 'F', '0', '0', 'master:warehouse:remove', '#', 'admin', current_timestamp, '', null, ''), (1086, '分配用户', 506, '5', '', '', '', '', 1, 0, 'F', '0', '0', 'master:warehouse:assign', '#', 'admin', current_timestamp, '', null, ''), (1087, '托盘查询', 507, '1', '', '', '', '', 1, 0, 'F', '0', '0', 'master:pallet:query', '#', 'admin', current_timestamp, '', null, ''), (1088, '托盘新增', 507, '2', '', '', '', '', 1, 0, 'F', '0', '0', 'master:pallet:add', '#', 'admin', current_timestamp, '', null, ''), (1089, '托盘修改', 507, '3', '', '', '', '', 1, 0, 'F', '0', '0', 'master:pallet:edit', '#', 'admin', current_timestamp, '', null, ''), -(1090, '托盘删除', 507, '4', '', '', '', '', 1, 0, 'F', '0', '0', 'master:pallet:remove', '#', 'admin', current_timestamp, '', null, ''), (1091, '托盘打印', 507, '5', '', '', '', '', 1, 0, 'F', '0', '0', 'master:pallet:print', '#', 'admin', current_timestamp, '', null, ''), (1092, '入库查询', 508, '1', '', '', '', '', 1, 0, 'F', '0', '0', 'business:inbound:query', '#', 'admin', current_timestamp, '', null, ''), (1093, '入库新增', 508, '2', '', '', '', '', 1, 0, 'F', '0', '0', 'business:inbound:add', '#', 'admin', current_timestamp, '', null, ''), (1094, '入库修改', 508, '3', '', '', '', '', 1, 0, 'F', '0', '0', 'business:inbound:edit', '#', 'admin', current_timestamp, '', null, ''), -(1095, '入库删除', 508, '4', '', '', '', '', 1, 0, 'F', '0', '0', 'business:inbound:remove', '#', 'admin', current_timestamp, '', null, ''), (1096, '入库确认', 508, '5', '', '', '', '', 1, 0, 'F', '0', '0', 'business:inbound:confirm', '#', 'admin', current_timestamp, '', null, ''), (1097, '装填查询', 509, '1', '', '', '', '', 1, 0, 'F', '0', '0', 'business:filling:query', '#', 'admin', current_timestamp, '', null, ''), (1098, '装填新增', 509, '2', '', '', '', '', 1, 0, 'F', '0', '0', 'business:filling:add', '#', 'admin', current_timestamp, '', null, ''), (1099, '发货查询', 510, '1', '', '', '', '', 1, 0, 'F', '0', '0', 'business:shipment:query', '#', 'admin', current_timestamp, '', null, ''), (1100, '发货新增', 510, '2', '', '', '', '', 1, 0, 'F', '0', '0', 'business:shipment:add', '#', 'admin', current_timestamp, '', null, ''), (1101, '发货修改', 510, '3', '', '', '', '', 1, 0, 'F', '0', '0', 'business:shipment:edit', '#', 'admin', current_timestamp, '', null, ''), -(1102, '发货删除', 510, '4', '', '', '', '', 1, 0, 'F', '0', '0', 'business:shipment:remove', '#', 'admin', current_timestamp, '', null, ''), (1103, '发货确认', 510, '5', '', '', '', '', 1, 0, 'F', '0', '0', 'business:shipment:confirm', '#', 'admin', current_timestamp, '', null, ''), (1104, '使用查询', 511, '1', '', '', '', '', 1, 0, 'F', '0', '0', 'business:usage:query', '#', 'admin', current_timestamp, '', null, ''), (1105, '使用新增', 511, '2', '', '', '', '', 1, 0, 'F', '0', '0', 'business:usage:add', '#', 'admin', current_timestamp, '', null, ''), (1106, '退回查询', 512, '1', '', '', '', '', 1, 0, 'F', '0', '0', 'business:return:query', '#', 'admin', current_timestamp, '', null, ''), (1107, '退回申请', 512, '2', '', '', '', '', 1, 0, 'F', '0', '0', 'business:return:request', '#', 'admin', current_timestamp, '', null, ''), -(1108, '退回确认', 512, '3', '', '', '', '', 1, 0, 'F', '0', '0', 'business:return:confirm', '#', 'admin', current_timestamp, '', null, ''); +(1108, '退回确认', 512, '3', '', '', '', '', 1, 0, 'F', '0', '0', 'business:return:confirm', '#', 'admin', current_timestamp, '', null, ''), +(1109, '批次查询', 519, '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:testbatch:query', '#', 'admin', current_timestamp, '', null, ''), +(1110, '批次新增', 519, '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:testbatch:add', '#', 'admin', current_timestamp, '', null, ''), +(1111, '批次修改', 519, '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:testbatch:edit', '#', 'admin', current_timestamp, '', null, ''), +(1112, '批次授权', 519, '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:testbatch:assign', '#', 'admin', current_timestamp, '', null, ''), +(1113, '批次关闭', 519, '5', '', '', '', '', 1, 0, 'F', '0', '0', 'system:testbatch:close', '#', 'admin', current_timestamp, '', null, ''), +(1114, '数据纳入', 519, '6', '', '', '', '', 1, 0, 'F', '0', '0', 'system:testbatch:adopt', '#', 'admin', current_timestamp, '', null, ''), +(1115, '批次清理', 519, '7', '', '', '', '', 1, 0, 'F', '0', '0', 'system:testbatch:purge', '#', 'admin', current_timestamp, '', null, ''); diff --git a/shierfeng-fastapi-backend/sql/shierfeng-fastapi.sql b/shierfeng-fastapi-backend/sql/shierfeng-fastapi.sql index a2451d2..0b80530 100644 --- a/shierfeng-fastapi-backend/sql/shierfeng-fastapi.sql +++ b/shierfeng-fastapi-backend/sql/shierfeng-fastapi.sql @@ -307,52 +307,52 @@ insert into sys_menu values('515', '发货出库', '123', '3', 'reportShipment', insert into sys_menu values('516', '产线消耗', '123', '4', 'consumption', 'report/consumption/index', '', '', 1, 0, 'C', '0', '0', 'report:consumption:list', '#', 'admin', sysdate(), '', null, '产线消耗菜单'); insert into sys_menu values('517', '退回仓库', '123', '5', 'reportReturn', 'report/return/index', '', '', 1, 0, 'C', '0', '0', 'report:return:list', '#', 'admin', sysdate(), '', null, '退回仓库报表菜单'); insert into sys_menu values('518', '托盘追溯', '123', '6', 'palletTrace', 'report/palletTrace/index', '', '', 1, 0, 'C', '0', '0', 'report:pallettrace:list', '#', 'admin', sysdate(), '', null, '托盘追溯菜单'); +insert into sys_menu values('519', '测试数据批次', '1', '7', 'testBatch', 'system/testBatch/index', '', '', 1, 0, 'C', '0', '0', 'system:testbatch:list', '#', 'admin', sysdate(), '', null, '测试数据批次菜单'); -- 托盘业务按钮 insert into sys_menu values('1065', '菌种查询', '502', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'master:strain:query', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1066', '菌种新增', '502', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'master:strain:add', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1067', '菌种修改', '502', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'master:strain:edit', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1068', '菌种删除', '502', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'master:strain:remove', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1069', '客户查询', '503', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'master:customer:query', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1070', '客户新增', '503', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'master:customer:add', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1071', '客户修改', '503', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'master:customer:edit', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1072', '客户删除', '503', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'master:customer:remove', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1073', '现场查询', '504', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'master:site:query', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1074', '现场新增', '504', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'master:site:add', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1075', '现场修改', '504', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'master:site:edit', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1076', '现场删除', '504', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'master:site:remove', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1077', '分配用户', '504', '5', '', '', '', '', 1, 0, 'F', '0', '0', 'master:site:assign', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1078', '产线查询', '505', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'master:line:query', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1079', '产线新增', '505', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'master:line:add', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1080', '产线修改', '505', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'master:line:edit', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1081', '产线删除', '505', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'master:line:remove', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1082', '仓库查询', '506', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'master:warehouse:query', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1083', '仓库新增', '506', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'master:warehouse:add', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1084', '仓库修改', '506', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'master:warehouse:edit', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1085', '仓库删除', '506', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'master:warehouse:remove', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1086', '分配用户', '506', '5', '', '', '', '', 1, 0, 'F', '0', '0', 'master:warehouse:assign', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1087', '托盘查询', '507', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'master:pallet:query', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1088', '托盘新增', '507', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'master:pallet:add', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1089', '托盘修改', '507', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'master:pallet:edit', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1090', '托盘删除', '507', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'master:pallet:remove', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1091', '托盘打印', '507', '5', '', '', '', '', 1, 0, 'F', '0', '0', 'master:pallet:print', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1092', '入库查询', '508', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'business:inbound:query', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1093', '入库新增', '508', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'business:inbound:add', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1094', '入库修改', '508', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'business:inbound:edit', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1095', '入库删除', '508', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'business:inbound:remove', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1096', '入库确认', '508', '5', '', '', '', '', 1, 0, 'F', '0', '0', 'business:inbound:confirm', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1097', '装填查询', '509', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'business:filling:query', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1098', '装填新增', '509', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'business:filling:add', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1099', '发货查询', '510', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'business:shipment:query', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1100', '发货新增', '510', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'business:shipment:add', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1101', '发货修改', '510', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'business:shipment:edit', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1102', '发货删除', '510', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'business:shipment:remove', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1103', '发货确认', '510', '5', '', '', '', '', 1, 0, 'F', '0', '0', 'business:shipment:confirm', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1104', '使用查询', '511', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'business:usage:query', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1105', '使用新增', '511', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'business:usage:add', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1106', '退回查询', '512', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'business:return:query', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1107', '退回申请', '512', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'business:return:request', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1108', '退回确认', '512', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'business:return:confirm', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1109', '批次查询', '519', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:testbatch:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1110', '批次新增', '519', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:testbatch:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1111', '批次修改', '519', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:testbatch:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1112', '批次授权', '519', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:testbatch:assign', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1113', '批次关闭', '519', '5', '', '', '', '', 1, 0, 'F', '0', '0', 'system:testbatch:close', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1114', '数据纳入', '519', '6', '', '', '', '', 1, 0, 'F', '0', '0', 'system:testbatch:adopt', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1115', '批次清理', '519', '7', '', '', '', '', 1, 0, 'F', '0', '0', 'system:testbatch:purge', '#', 'admin', sysdate(), '', null, ''); -- ---------------------------- diff --git a/shierfeng-fastapi-backend/tests/module_business/test_api_permission_contract.py b/shierfeng-fastapi-backend/tests/module_business/test_api_permission_contract.py index 826eb46..956c099 100644 --- a/shierfeng-fastapi-backend/tests/module_business/test_api_permission_contract.py +++ b/shierfeng-fastapi-backend/tests/module_business/test_api_permission_contract.py @@ -24,11 +24,13 @@ CONTROLLER_NAMES = ( 'usage', 'return', 'report', + 'test_batch', ) TARGET_PREFIXES = ( '/master', '/business', '/report', + '/system/test-batch', ) ROUTE_HTTP_METHODS = frozenset({'DELETE', 'GET', 'HEAD', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'TRACE'}) WRITE_METHODS = frozenset({'DELETE', 'PATCH', 'POST', 'PUT'}) @@ -52,6 +54,7 @@ EXPECTED_PAGE_PERMISSIONS = frozenset( 'report:consumption:list', 'report:return:list', 'report:pallettrace:list', + 'system:testbatch:list', } ) EXPECTED_BUTTON_PERMISSIONS = frozenset( @@ -59,50 +62,54 @@ EXPECTED_BUTTON_PERMISSIONS = frozenset( 'master:strain:query', 'master:strain:add', 'master:strain:edit', - 'master:strain:remove', 'master:customer:query', 'master:customer:add', 'master:customer:edit', - 'master:customer:remove', 'master:site:query', 'master:site:add', 'master:site:edit', - 'master:site:remove', 'master:site:assign', 'master:line:query', 'master:line:add', 'master:line:edit', - 'master:line:remove', 'master:warehouse:query', 'master:warehouse:add', 'master:warehouse:edit', - 'master:warehouse:remove', 'master:warehouse:assign', 'master:pallet:query', 'master:pallet:add', 'master:pallet:edit', - 'master:pallet:remove', 'master:pallet:print', 'business:inbound:query', 'business:inbound:add', 'business:inbound:edit', - 'business:inbound:remove', 'business:inbound:confirm', 'business:filling:query', 'business:filling:add', 'business:shipment:query', 'business:shipment:add', 'business:shipment:edit', - 'business:shipment:remove', 'business:shipment:confirm', 'business:usage:query', 'business:usage:add', 'business:return:query', 'business:return:request', 'business:return:confirm', + 'system:testbatch:query', + 'system:testbatch:add', + 'system:testbatch:edit', + 'system:testbatch:assign', + 'system:testbatch:close', + 'system:testbatch:adopt', + 'system:testbatch:purge', } ) EXPECTED_PERMISSIONS = EXPECTED_PAGE_PERMISSIONS | EXPECTED_BUTTON_PERMISSIONS +EXPECTED_WEB_PERMISSIONS = frozenset( + permission + for permission in EXPECTED_PERMISSIONS + if not permission.startswith('system:testbatch:') or permission == 'system:testbatch:adopt' +) VUE_PAGE_PATHS = ( 'src/views/master/strain/index.vue', 'src/views/master/customer/index.vue', @@ -122,7 +129,30 @@ VUE_PAGE_PATHS = ( 'src/views/report/return/index.vue', 'src/views/report/palletTrace/index.vue', ) -PERMISSION_LITERAL_PATTERN = re.compile(r'(?:master|business|report):[a-z]+:[a-z]+') +PERMISSION_LITERAL_PATTERN = re.compile(r'(?:master|business|report|system):[a-z]+:[a-z]+') +UNGUARDED_AUTHENTICATED_ROUTES = {('/system/test-batch/options', 'GET')} +REMOVED_DELETE_SYMBOLS = ( + 'delStrain', + 'delCustomer', + 'delSite', + 'delLine', + 'delWarehouse', + 'delPallet', + 'delInbound', + 'delShipment', +) +REMOVED_DELETE_PERMISSIONS = frozenset( + { + 'master:strain:remove', + 'master:customer:remove', + 'master:site:remove', + 'master:line:remove', + 'master:warehouse:remove', + 'master:pallet:remove', + 'business:inbound:remove', + 'business:shipment:remove', + } +) def _build_expected_route_permissions() -> dict[tuple[str, str], str]: @@ -136,7 +166,7 @@ def _build_expected_route_permissions() -> dict[tuple[str, str], str]: (f'{root}/{{{resource}_id}}', 'GET'): f'{permission_root}:query', (root, 'POST'): f'{permission_root}:add', (root, 'PUT'): f'{permission_root}:edit', - (f'{root}/{{{resource}_ids}}', 'DELETE'): f'{permission_root}:remove', + (f'{root}/{{{resource}_ids}}', 'DELETE'): f'{permission_root}:list', } ) @@ -153,7 +183,7 @@ def _build_expected_route_permissions() -> dict[tuple[str, str], str]: ('/business/inbound/{inbound_id}', 'GET'): 'business:inbound:query', ('/business/inbound', 'POST'): 'business:inbound:add', ('/business/inbound', 'PUT'): 'business:inbound:edit', - ('/business/inbound/{inbound_ids}', 'DELETE'): 'business:inbound:remove', + ('/business/inbound/{inbound_ids}', 'DELETE'): 'business:inbound:list', ('/business/inbound/{inbound_id}/confirm', 'PUT'): 'business:inbound:confirm', ('/business/filling/list', 'GET'): 'business:filling:list', ('/business/filling/strain-options', 'GET'): 'business:filling:query', @@ -167,7 +197,7 @@ def _build_expected_route_permissions() -> dict[tuple[str, str], str]: ('/business/shipment/{shipment_id}', 'GET'): 'business:shipment:query', ('/business/shipment', 'POST'): 'business:shipment:add', ('/business/shipment', 'PUT'): 'business:shipment:edit', - ('/business/shipment/{shipment_ids}', 'DELETE'): 'business:shipment:remove', + ('/business/shipment/{shipment_ids}', 'DELETE'): 'business:shipment:list', ('/business/shipment/{shipment_id}/confirm', 'PUT'): 'business:shipment:confirm', ('/business/usage/list', 'GET'): 'business:usage:list', ('/business/usage/line-options', 'GET'): 'business:usage:query', @@ -186,6 +216,17 @@ def _build_expected_route_permissions() -> dict[tuple[str, str], str]: ('/report/consumption/list', 'GET'): 'report:consumption:list', ('/report/return/list', 'GET'): 'report:return:list', ('/report/pallet-trace/list', 'GET'): 'report:pallettrace:list', + ('/system/test-batch/list', 'GET'): 'system:testbatch:list', + ('/system/test-batch', 'POST'): 'system:testbatch:add', + ('/system/test-batch/{batch_id}', 'PUT'): 'system:testbatch:edit', + ('/system/test-batch/{batch_id}/users', 'PUT'): 'system:testbatch:assign', + ('/system/test-batch/{batch_id}/close', 'PUT'): 'system:testbatch:close', + ('/system/test-batch/{batch_id}/reopen', 'PUT'): 'system:testbatch:close', + ('/system/test-batch/{batch_id}/adoption/preview', 'POST'): 'system:testbatch:adopt', + ('/system/test-batch/{batch_id}/adoption/execute', 'POST'): 'system:testbatch:adopt', + ('/system/test-batch/{batch_id}/purge/preview', 'POST'): 'system:testbatch:purge', + ('/system/test-batch/{batch_id}/purge', 'POST'): 'system:testbatch:purge', + ('/system/test-batch/{batch_id}', 'GET'): 'system:testbatch:query', } ) return expected @@ -214,6 +255,10 @@ STATIC_READS_BEFORE_DETAIL = { '/business/return/warehouse-options', '/business/return/scan/{qr_token}', ), + '/system/test-batch/{batch_id}': ( + '/system/test-batch/options', + '/system/test-batch/list', + ), } EXACT_QUERY_PARAMETERS = { ('/business/inbound/scan/{qr_token}', 'GET'): {'warehouseId'}, @@ -241,7 +286,7 @@ def _auth_dependencies(route: APIRoute) -> list[CheckUserInterfaceAuth]: def _assert_controller_permission_literals(test_case: unittest.TestCase, literals: list[str]) -> None: expected = Counter(EXPECTED_ROUTE_PERMISSIONS.values()) - test_case.assertEqual(74, expected.total()) + test_case.assertEqual(85, expected.total()) test_case.assertEqual(expected, Counter(literals)) test_case.assertEqual(EXPECTED_PERMISSIONS, frozenset(expected)) @@ -368,10 +413,18 @@ class ApiPermissionContractTest(unittest.TestCase): ] def test_target_path_selector_uses_namespace_boundaries(self) -> None: - for path in ('/master', '/master/foo', '/business', '/business/foo', '/report/foo'): + for path in ( + '/master', + '/master/foo', + '/business', + '/business/foo', + '/report/foo', + '/system/test-batch', + '/system/test-batch/options', + ): with self.subTest(path=path): self.assertTrue(_is_target_path(path)) - for path in ('/masterish', '/business-other', '/reporting'): + for path in ('/masterish', '/business-other', '/reporting', '/system/test-batches'): with self.subTest(path=path): self.assertFalse(_is_target_path(path)) @@ -384,8 +437,8 @@ class ApiPermissionContractTest(unittest.TestCase): if method.upper() in ROUTE_HTTP_METHODS } - self.assertEqual(set(EXPECTED_ROUTE_PERMISSIONS), actual_operations) - self.assertEqual(74, len(actual_operations)) + self.assertEqual(set(EXPECTED_ROUTE_PERMISSIONS) | UNGUARDED_AUTHENTICATED_ROUTES, actual_operations) + self.assertEqual(86, len(actual_operations)) openapi_ai_paths = sorted(path for path in self.schema['paths'] if path == '/ai' or path.startswith('/ai/')) app_ai_paths = sorted( @@ -406,8 +459,11 @@ class ApiPermissionContractTest(unittest.TestCase): operation_keys.append(key) auth_dependencies = _auth_dependencies(route) - self.assertEqual(1, len(auth_dependencies), key) - actual_permissions[key] = auth_dependencies[0].perm + if key in UNGUARDED_AUTHENTICATED_ROUTES: + self.assertEqual([], auth_dependencies, key) + else: + self.assertEqual(1, len(auth_dependencies), key) + actual_permissions[key] = auth_dependencies[0].perm self.assertEqual(len(operation_keys), len(set(operation_keys))) self.assertEqual(EXPECTED_ROUTE_PERMISSIONS, actual_permissions) @@ -435,19 +491,19 @@ class ApiPermissionContractTest(unittest.TestCase): literals.append(node.args[0].value) self.assertEqual([], malformed_calls) - self.assertEqual(17, len(EXPECTED_PAGE_PERMISSIONS)) - self.assertEqual(44, len(EXPECTED_BUTTON_PERMISSIONS)) + self.assertEqual(18, len(EXPECTED_PAGE_PERMISSIONS)) + self.assertEqual(43, len(EXPECTED_BUTTON_PERMISSIONS)) self.assertFalse(EXPECTED_PAGE_PERMISSIONS & EXPECTED_BUTTON_PERMISSIONS) self.assertEqual(61, len(EXPECTED_PERMISSIONS)) _assert_controller_permission_literals(self, literals) self.assertEqual( - {'add', 'assign', 'confirm', 'edit', 'list', 'print', 'query', 'remove', 'request'}, + {'add', 'adopt', 'assign', 'close', 'confirm', 'edit', 'list', 'print', 'purge', 'query', 'request'}, {permission.rsplit(':', maxsplit=1)[1] for permission in literals}, ) def test_controller_ast_permission_contract_rejects_duplicate_call(self) -> None: literals = list(EXPECTED_ROUTE_PERMISSIONS.values()) - self.assertEqual(74, len(literals)) + self.assertEqual(85, len(literals)) literals.append(literals[0]) with self.assertRaises(AssertionError): @@ -473,7 +529,66 @@ class ApiPermissionContractTest(unittest.TestCase): self.assertTrue(guarded, relative_path) actual_permissions.update(guarded) - _assert_exact_permission_layers(self, {'vue': actual_permissions}) + self.assertEqual(EXPECTED_WEB_PERMISSIONS, frozenset(actual_permissions)) + + def test_frontend_has_no_physical_delete_api_or_page_contract(self) -> None: + relative_paths = ( + 'src/api/master/strain.js', + 'src/api/master/customer.js', + 'src/api/master/site.js', + 'src/api/master/line.js', + 'src/api/master/warehouse.js', + 'src/api/master/pallet.js', + 'src/api/business/inbound.js', + 'src/api/business/shipment.js', + *VUE_PAGE_PATHS, + ) + for relative_path in relative_paths: + source = (FRONTEND_ROOT / relative_path).read_text(encoding='utf-8') + code, structure = _sanitize_vue_source(source) + with self.subTest(relative_path=relative_path): + for symbol in REMOVED_DELETE_SYMBOLS: + self.assertIsNone(re.search(rf'\b{symbol}\b', structure), symbol) + self.assertFalse(REMOVED_DELETE_PERMISSIONS & set(PERMISSION_LITERAL_PATTERN.findall(code))) + + def test_sensitive_test_batch_logs_only_include_batch_id_and_response_summary(self) -> None: + source_path = BACKEND_ROOT / 'module_business/controller/test_batch_controller.py' + tree = ast.parse(source_path.read_text(encoding='utf-8'), filename=str(source_path)) + functions = {node.name: node for node in tree.body if isinstance(node, ast.AsyncFunctionDef)} + sensitive_handlers = { + 'preview_test_batch_adoption', + 'execute_test_batch_adoption', + 'preview_test_batch_purge', + 'purge_test_batch', + } + self.assertEqual(sensitive_handlers, sensitive_handlers & functions.keys()) + + for handler_name in sensitive_handlers: + log_calls = [ + decorator + for decorator in functions[handler_name].decorator_list + if isinstance(decorator, ast.Call) + and isinstance(decorator.func, ast.Name) + and decorator.func.id == 'Log' + ] + with self.subTest(handler=handler_name): + self.assertEqual(1, len(log_calls)) + keywords = {keyword.arg: keyword.value for keyword in log_calls[0].keywords} + self.assertEqual('include', ast.literal_eval(keywords['request_log_mode'])) + self.assertEqual('summary', ast.literal_eval(keywords['response_log_mode'])) + include_fields = keywords['request_include_fields'] + self.assertIsInstance(include_fields, ast.Tuple) + self.assertEqual(1, len(include_fields.elts)) + field_call = include_fields.elts[0] + self.assertIsInstance(field_call, ast.Call) + self.assertEqual('batch_id', ast.literal_eval(field_call.args[0])) + self.assertIsInstance(field_call.func, ast.Attribute) + self.assertEqual('field', field_call.func.attr) + root = field_call.func.value + self.assertIsInstance(root, ast.Attribute) + self.assertEqual('PATH_PARAMS', root.attr) + self.assertIsInstance(root.value, ast.Name) + self.assertEqual('RequestLogFieldRoot', root.value.id) def test_vue_guard_parser_rejects_dynamic_arrays_and_has_permi_or(self) -> None: dynamic_sources = ( @@ -561,7 +676,7 @@ class ApiPermissionContractTest(unittest.TestCase): self.assertLess(route_order[static_path], route_order[detail_path]) def test_openapi_parameter_names_are_exact(self) -> None: - for path, method in EXPECTED_ROUTE_PERMISSIONS: + for path, method in set(EXPECTED_ROUTE_PERMISSIONS) | UNGUARDED_AUTHENTICATED_ROUTES: parameters = self.schema['paths'][path][method.lower()].get('parameters', []) actual_path_parameters = {parameter['name'] for parameter in parameters if parameter['in'] == 'path'} self.assertEqual(set(re.findall(r'{([^{}]+)}', path)), actual_path_parameters, (path, method)) @@ -593,7 +708,7 @@ class ApiPermissionContractTest(unittest.TestCase): for method in route.methods & WRITE_METHODS: actual_write_routes[(route.path, method)] = route - self.assertEqual(32, len(expected_write_permissions)) + self.assertEqual(41, len(expected_write_permissions)) self.assertEqual(set(expected_write_permissions), set(actual_write_routes)) for key, expected_permission in expected_write_permissions.items(): with self.subTest(operation=key): diff --git a/shierfeng-fastapi-backend/tests/module_business/test_sql_contract.py b/shierfeng-fastapi-backend/tests/module_business/test_sql_contract.py index 184d46a..5176ed1 100644 --- a/shierfeng-fastapi-backend/tests/module_business/test_sql_contract.py +++ b/shierfeng-fastapi-backend/tests/module_business/test_sql_contract.py @@ -446,6 +446,7 @@ EXPECTED_CHECKS.update( ) EXPECTED_PAGES = { + 519: ('测试数据批次', 1, 7, 'testBatch', 'system/testBatch/index', 'system:testbatch:list'), 502: ('菌种资料', 121, 1, 'strain', 'master/strain/index', 'master:strain:list'), 503: ('客户资料', 121, 2, 'customer', 'master/customer/index', 'master:customer:list'), 504: ('客户现场', 121, 3, 'site', 'master/site/index', 'master:site:list'), @@ -476,47 +477,46 @@ BUTTON_SPECS = """ 1065 502 1 菌种查询 master:strain:query 1066 502 2 菌种新增 master:strain:add 1067 502 3 菌种修改 master:strain:edit -1068 502 4 菌种删除 master:strain:remove 1069 503 1 客户查询 master:customer:query 1070 503 2 客户新增 master:customer:add 1071 503 3 客户修改 master:customer:edit -1072 503 4 客户删除 master:customer:remove 1073 504 1 现场查询 master:site:query 1074 504 2 现场新增 master:site:add 1075 504 3 现场修改 master:site:edit -1076 504 4 现场删除 master:site:remove 1077 504 5 分配用户 master:site:assign 1078 505 1 产线查询 master:line:query 1079 505 2 产线新增 master:line:add 1080 505 3 产线修改 master:line:edit -1081 505 4 产线删除 master:line:remove 1082 506 1 仓库查询 master:warehouse:query 1083 506 2 仓库新增 master:warehouse:add 1084 506 3 仓库修改 master:warehouse:edit -1085 506 4 仓库删除 master:warehouse:remove 1086 506 5 分配用户 master:warehouse:assign 1087 507 1 托盘查询 master:pallet:query 1088 507 2 托盘新增 master:pallet:add 1089 507 3 托盘修改 master:pallet:edit -1090 507 4 托盘删除 master:pallet:remove 1091 507 5 托盘打印 master:pallet:print 1092 508 1 入库查询 business:inbound:query 1093 508 2 入库新增 business:inbound:add 1094 508 3 入库修改 business:inbound:edit -1095 508 4 入库删除 business:inbound:remove 1096 508 5 入库确认 business:inbound:confirm 1097 509 1 装填查询 business:filling:query 1098 509 2 装填新增 business:filling:add 1099 510 1 发货查询 business:shipment:query 1100 510 2 发货新增 business:shipment:add 1101 510 3 发货修改 business:shipment:edit -1102 510 4 发货删除 business:shipment:remove 1103 510 5 发货确认 business:shipment:confirm 1104 511 1 使用查询 business:usage:query 1105 511 2 使用新增 business:usage:add 1106 512 1 退回查询 business:return:query 1107 512 2 退回申请 business:return:request 1108 512 3 退回确认 business:return:confirm +1109 519 1 批次查询 system:testbatch:query +1110 519 2 批次新增 system:testbatch:add +1111 519 3 批次修改 system:testbatch:edit +1112 519 4 批次授权 system:testbatch:assign +1113 519 5 批次关闭 system:testbatch:close +1114 519 6 数据纳入 system:testbatch:adopt +1115 519 7 批次清理 system:testbatch:purge """ EXPECTED_BUTTONS = { int(menu_id): (name, int(parent_id), int(order_num), permission) @@ -526,6 +526,7 @@ EXPECTED_BUTTONS = { } EXPECTED_PAGE_PERMISSIONS = frozenset( [ + 'system:testbatch:list', 'master:strain:list', 'master:customer:list', 'master:site:list', @@ -550,41 +551,40 @@ EXPECTED_BUTTON_PERMISSIONS = frozenset( 'master:strain:query', 'master:strain:add', 'master:strain:edit', - 'master:strain:remove', 'master:customer:query', 'master:customer:add', 'master:customer:edit', - 'master:customer:remove', 'master:site:query', 'master:site:add', 'master:site:edit', - 'master:site:remove', 'master:site:assign', 'master:line:query', 'master:line:add', 'master:line:edit', - 'master:line:remove', 'master:warehouse:query', 'master:warehouse:add', 'master:warehouse:edit', - 'master:warehouse:remove', 'master:warehouse:assign', 'master:pallet:query', 'master:pallet:add', 'master:pallet:edit', - 'master:pallet:remove', 'master:pallet:print', 'business:inbound:query', 'business:inbound:add', 'business:inbound:edit', - 'business:inbound:remove', 'business:inbound:confirm', 'business:filling:query', 'business:filling:add', 'business:shipment:query', 'business:shipment:add', 'business:shipment:edit', - 'business:shipment:remove', + 'system:testbatch:query', + 'system:testbatch:add', + 'system:testbatch:edit', + 'system:testbatch:assign', + 'system:testbatch:close', + 'system:testbatch:adopt', + 'system:testbatch:purge', 'business:shipment:confirm', 'business:usage:query', 'business:usage:add', @@ -595,6 +595,7 @@ EXPECTED_BUTTON_PERMISSIONS = frozenset( ) EXPECTED_PERMISSIONS = EXPECTED_PAGE_PERMISSIONS | EXPECTED_BUTTON_PERMISSIONS BUSINESS_MENU_SPECS = """ +519|1|测试数据批次|testBatch||7|system:testbatch:list|system/testBatch/index 121|0|基础资料维护|businessMaster||1|| 122|0|业务管理|businessOperation||2|| 123|0|报表查询|businessReport||3|| @@ -618,47 +619,46 @@ BUSINESS_MENU_SPECS = """ 1065|502|菌种查询|||1|master:strain:query| 1066|502|菌种新增|||2|master:strain:add| 1067|502|菌种修改|||3|master:strain:edit| -1068|502|菌种删除|||4|master:strain:remove| 1069|503|客户查询|||1|master:customer:query| 1070|503|客户新增|||2|master:customer:add| 1071|503|客户修改|||3|master:customer:edit| -1072|503|客户删除|||4|master:customer:remove| 1073|504|现场查询|||1|master:site:query| 1074|504|现场新增|||2|master:site:add| 1075|504|现场修改|||3|master:site:edit| -1076|504|现场删除|||4|master:site:remove| 1077|504|分配用户|||5|master:site:assign| 1078|505|产线查询|||1|master:line:query| 1079|505|产线新增|||2|master:line:add| 1080|505|产线修改|||3|master:line:edit| -1081|505|产线删除|||4|master:line:remove| 1082|506|仓库查询|||1|master:warehouse:query| 1083|506|仓库新增|||2|master:warehouse:add| 1084|506|仓库修改|||3|master:warehouse:edit| -1085|506|仓库删除|||4|master:warehouse:remove| 1086|506|分配用户|||5|master:warehouse:assign| 1087|507|托盘查询|||1|master:pallet:query| 1088|507|托盘新增|||2|master:pallet:add| 1089|507|托盘修改|||3|master:pallet:edit| -1090|507|托盘删除|||4|master:pallet:remove| 1091|507|托盘打印|||5|master:pallet:print| 1092|508|入库查询|||1|business:inbound:query| 1093|508|入库新增|||2|business:inbound:add| 1094|508|入库修改|||3|business:inbound:edit| -1095|508|入库删除|||4|business:inbound:remove| 1096|508|入库确认|||5|business:inbound:confirm| 1097|509|装填查询|||1|business:filling:query| 1098|509|装填新增|||2|business:filling:add| 1099|510|发货查询|||1|business:shipment:query| 1100|510|发货新增|||2|business:shipment:add| 1101|510|发货修改|||3|business:shipment:edit| -1102|510|发货删除|||4|business:shipment:remove| 1103|510|发货确认|||5|business:shipment:confirm| 1104|511|使用查询|||1|business:usage:query| 1105|511|使用新增|||2|business:usage:add| 1106|512|退回查询|||1|business:return:query| 1107|512|退回申请|||2|business:return:request| 1108|512|退回确认|||3|business:return:confirm| +1109|519|批次查询|||1|system:testbatch:query| +1110|519|批次新增|||2|system:testbatch:add| +1111|519|批次修改|||3|system:testbatch:edit| +1112|519|批次授权|||4|system:testbatch:assign| +1113|519|批次关闭|||5|system:testbatch:close| +1114|519|数据纳入|||6|system:testbatch:adopt| +1115|519|批次清理|||7|system:testbatch:purge| """.strip() @@ -679,7 +679,33 @@ def _expected_business_menu_fields() -> dict[int, tuple]: EXPECTED_BUSINESS_MENU_FIELDS = _expected_business_menu_fields() -LEGACY_PATCH_BUSINESS_MENU_FIELDS = EXPECTED_BUSINESS_MENU_FIELDS | { + +LEGACY_REMOVED_BUTTON_SPECS = { + 1068: ('菌种删除', 502, 4, 'master:strain:remove'), + 1072: ('客户删除', 503, 4, 'master:customer:remove'), + 1076: ('现场删除', 504, 4, 'master:site:remove'), + 1081: ('产线删除', 505, 4, 'master:line:remove'), + 1085: ('仓库删除', 506, 4, 'master:warehouse:remove'), + 1090: ('托盘删除', 507, 4, 'master:pallet:remove'), + 1095: ('入库删除', 508, 4, 'business:inbound:remove'), + 1102: ('发货删除', 510, 4, 'business:shipment:remove'), +} +LEGACY_BUSINESS_MENU_IDS = set(range(121, 124)) | set(range(502, 519)) | set(range(1065, 1109)) +LEGACY_EXPECTED_BUSINESS_MENU_FIELDS = { + menu_id: fields for menu_id, fields in EXPECTED_BUSINESS_MENU_FIELDS.items() if menu_id in LEGACY_BUSINESS_MENU_IDS +} +LEGACY_EXPECTED_BUSINESS_MENU_FIELDS.update( + { + menu_id: (parent_id, name, '', '', order_num, permission, '') + for menu_id, (name, parent_id, order_num, permission) in LEGACY_REMOVED_BUTTON_SPECS.items() + } +) +LEGACY_EXPECTED_PAGE_PERMISSIONS = EXPECTED_PAGE_PERMISSIONS - {'system:testbatch:list'} +LEGACY_EXPECTED_BUTTON_PERMISSIONS = EXPECTED_BUTTON_PERMISSIONS - { + permission for permission in EXPECTED_BUTTON_PERMISSIONS if permission.startswith('system:testbatch:') +} | {spec[3] for spec in LEGACY_REMOVED_BUTTON_SPECS.values()} +LEGACY_EXPECTED_PERMISSIONS = LEGACY_EXPECTED_PAGE_PERMISSIONS | LEGACY_EXPECTED_BUTTON_PERMISSIONS +LEGACY_PATCH_BUSINESS_MENU_FIELDS = LEGACY_EXPECTED_BUSINESS_MENU_FIELDS | { 121: (1, '基础资料维护', 'businessMaster', '', 10, '', None), 122: (1, '业务管理', 'businessOperation', '', 11, '', None), 123: (1, '报表查询', 'businessReport', '', 12, '', None), @@ -706,7 +732,28 @@ EXPECTED_TOP_LEVEL_MENU_STABLE_FIELDS = { 3: ('系统工具', 'tool', None, '', '', '1', '0', 'M', '0', '0', '', 'tool'), } MENU_PROMOTION_IDS = set(EXPECTED_TOP_LEVEL_MENU_POSITIONS) -BUSINESS_MENU_IDS = set(range(121, 124)) | set(range(502, 519)) | set(range(1065, 1109)) +BUSINESS_MENU_IDS = ( + set(range(121, 124)) + | set(range(502, 519)) + | {519} + | set(range(1065, 1068)) + | set(range(1069, 1072)) + | set(range(1073, 1076)) + | {1077} + | set(range(1078, 1081)) + | set(range(1082, 1085)) + | {1086} + | set(range(1087, 1090)) + | {1091} + | set(range(1092, 1095)) + | {1096} + | set(range(1097, 1099)) + | set(range(1099, 1102)) + | {1103} + | set(range(1104, 1109)) + | set(range(1109, 1116)) +) +TEST_BATCH_MENU_IDS = {519, 1109, 1110, 1111, 1112, 1113, 1114, 1115} MYSQL_MENU_STABLE_COLUMNS = ( 'menu_name', 'parent_id', @@ -1022,13 +1069,11 @@ def _assert_test_batch_sql_contract( 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*\)' - ) + 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+" + rf'comment\s+on\s+column\s+{table_name}\.test_batch_id\s+is\s+' r"'测试数据批次id;null表示正式数据'", ) @@ -1049,8 +1094,8 @@ def _menu_rows(sql: str, menu_ids: set[int]) -> dict[int, list]: return {int(row[0]): row for row in selected} -def _business_menu_rows(sql: str) -> dict[int, list]: - return _menu_rows(sql, BUSINESS_MENU_IDS) +def _business_menu_rows(sql: str, menu_ids: set[int] | None = None) -> dict[int, list]: + return _menu_rows(sql, BUSINESS_MENU_IDS if menu_ids is None else menu_ids) def _assert_top_level_menu_fields(test_case: unittest.TestCase, sql: str) -> None: @@ -1067,7 +1112,7 @@ def _assert_business_menu_fields( sql: str, expected_fields: dict[int, tuple] = EXPECTED_BUSINESS_MENU_FIELDS, ) -> None: - rows = _business_menu_rows(sql) + rows = _business_menu_rows(sql, set(expected_fields)) invalid_widths = {menu_id: len(row) for menu_id, row in rows.items() if len(row) != SYS_MENU_VALUE_COUNT} test_case.assertEqual({}, invalid_widths) actual = { @@ -1085,17 +1130,21 @@ def _assert_business_menu_fields( test_case.assertEqual(expected_fields, actual) -def _assert_business_permission_inventory(test_case: unittest.TestCase, sql: str) -> None: +def _assert_business_permission_inventory( + test_case: unittest.TestCase, + sql: str, + expected_permissions: frozenset[str] = EXPECTED_PERMISSIONS, +) -> None: permission_rows = [ row for row in _insert_rows(sql, 'sys_menu') if len(row) > SYS_MENU_PERMS_INDEX and row[SYS_MENU_PERMS_INDEX] - and re.match(r'^(?:master|business|report):', row[SYS_MENU_PERMS_INDEX]) + and re.match(r'^(?:master|business|report|system:testbatch):', row[SYS_MENU_PERMS_INDEX]) ] test_case.assertTrue(all(len(row) == SYS_MENU_VALUE_COUNT for row in permission_rows)) actual_permissions = Counter(row[SYS_MENU_PERMS_INDEX] for row in permission_rows) - test_case.assertEqual(Counter(EXPECTED_PERMISSIONS), actual_permissions) + test_case.assertEqual(Counter(expected_permissions), actual_permissions) MENU_PROMOTION_LOCK_STATEMENT = ( @@ -1310,6 +1359,57 @@ class SqlContractTest(unittest.TestCase): is_patch=is_patch, ) + 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): + with self.subTest(path=path.name): + raw = path.read_text(encoding='utf-8') + statements = _sql_statements(raw) + normalized = [_normalized_sql_statement(statement) for statement in statements] + + role_delete_indexes = [ + index + for index, statement in enumerate(normalized) + if re.fullmatch(r'delete from sys_role_menu where menu_id in \([^)]+\)', statement) + ] + menu_delete_indexes = [ + index + for index, statement in enumerate(normalized) + if re.fullmatch(r'delete from sys_menu where menu_id in \([^)]+\)', statement) + ] + menu_insert_indexes = [ + index + for index, statement in enumerate(normalized) + if statement.startswith('insert into sys_menu values ') + ] + self.assertEqual(1, len(role_delete_indexes)) + self.assertEqual(1, len(menu_delete_indexes)) + self.assertEqual(1, len(menu_insert_indexes)) + role_delete = normalized[role_delete_indexes[0]] + menu_delete = normalized[menu_delete_indexes[0]] + role_delete_ids = { + int(value.strip()) for value in re.search(r'\(([^)]+)\)', role_delete).group(1).split(',') + } + menu_delete_ids = { + int(value.strip()) for value in re.search(r'\(([^)]+)\)', menu_delete).group(1).split(',') + } + self.assertEqual(legacy_removed_ids, role_delete_ids) + self.assertEqual(legacy_removed_ids, menu_delete_ids) + self.assertLess(role_delete_indexes[0], menu_delete_indexes[0]) + self.assertLess(menu_delete_indexes[0], menu_insert_indexes[0]) + + inserted_rows = _insert_rows(raw, 'sys_menu') + inserted_by_id = {int(row[0]): row for row in inserted_rows} + self.assertEqual(TEST_BATCH_MENU_IDS, set(inserted_by_id)) + self.assertTrue(all(len(row) == SYS_MENU_VALUE_COUNT for row in inserted_by_id.values())) + self.assertEqual('测试数据批次', inserted_by_id[519][1]) + self.assertEqual('1', inserted_by_id[519][2]) + self.assertEqual('7', inserted_by_id[519][3]) + self.assertEqual('system:testbatch:list', inserted_by_id[519][13]) + self.assertEqual(519, int(inserted_by_id[1109][2])) + self.assertEqual('system:testbatch:purge', inserted_by_id[1115][13]) + self.assertNotRegex('\n'.join(normalized), r'\binsert into sys_role_menu\b') + def test_initializers_define_exact_business_table_set(self) -> None: for filename in INITIALIZERS: with self.subTest(filename=filename): @@ -1357,8 +1457,8 @@ class SqlContractTest(unittest.TestCase): 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(17, len(EXPECTED_PAGE_PERMISSIONS)) - self.assertEqual(44, len(EXPECTED_BUTTON_PERMISSIONS)) + self.assertEqual(18, len(EXPECTED_PAGE_PERMISSIONS)) + self.assertEqual(43, len(EXPECTED_BUTTON_PERMISSIONS)) self.assertFalse(EXPECTED_PAGE_PERMISSIONS & EXPECTED_BUTTON_PERMISSIONS) self.assertEqual(61, len(EXPECTED_PERMISSIONS)) self.assertEqual(64, len(EXPECTED_BUSINESS_MENU_FIELDS)) @@ -1969,20 +2069,26 @@ class SqlContractTest(unittest.TestCase): self.assertEqual(LEGACY_EXPECTED_FOREIGN_KEYS[table], foreign_keys) self.assertEqual(LEGACY_EXPECTED_CHECKS[table], checks) _assert_business_menu_fields(self, raw, LEGACY_PATCH_BUSINESS_MENU_FIELDS) - _assert_business_permission_inventory(self, raw) - patch_rows = _business_menu_rows(raw) + _assert_business_permission_inventory(self, raw, LEGACY_EXPECTED_PERMISSIONS) + patch_rows = _business_menu_rows(raw, LEGACY_BUSINESS_MENU_IDS) mysql_rows = _business_menu_rows((SQL_ROOT / 'shierfeng-fastapi.sql').read_text(encoding='utf-8')) compared_indexes = (0, 1, *range(4, 15)) + common_menu_ids = LEGACY_BUSINESS_MENU_IDS & BUSINESS_MENU_IDS expected_legacy_rows = { menu_id: tuple( '#' if index == SYS_MENU_ICON_INDEX and menu_id in EXPECTED_BUSINESS_ROOT_ICONS else row[index] for index in compared_indexes ) for menu_id, row in mysql_rows.items() + if menu_id in common_menu_ids } self.assertEqual( expected_legacy_rows, - {menu_id: tuple(row[index] for index in compared_indexes) for menu_id, row in patch_rows.items()}, + { + menu_id: tuple(row[index] for index in compared_indexes) + for menu_id, row in patch_rows.items() + if menu_id in common_menu_ids + }, ) self.assertRegex(lower, r'\bon\s+duplicate\s+key\s+update\b') self.assertNotRegex(lower, r'\binsert\s+ignore\b') @@ -2017,7 +2123,7 @@ class SqlContractTest(unittest.TestCase): flags=re.S, ) self.assertIsNotNone(menu_insert, 'sys_menu seed must use a fail-closed upsert') - self.assertEqual(len(BUSINESS_MENU_IDS), len(_insert_rows(menu_insert_statement, 'sys_menu'))) + self.assertEqual(len(LEGACY_BUSINESS_MENU_IDS), len(_insert_rows(menu_insert_statement, 'sys_menu'))) expected_guard = ' and '.join(f'{column} <=> values({column})' for column in MYSQL_MENU_STABLE_COLUMNS) actual_guard = re.sub(r'\s+', ' ', menu_insert.group('guard')).strip() self.assertEqual(expected_guard, actual_guard) @@ -2039,8 +2145,8 @@ class SqlContractTest(unittest.TestCase): verification_columns = tuple(column.strip() for column in verification.group('columns').split(',')) self.assertEqual(('menu_id', *MYSQL_MENU_STABLE_COLUMNS), verification_columns) verification_ids = [int(value.strip()) for value in verification.group('ids').split(',')] - self.assertEqual(len(BUSINESS_MENU_IDS), len(verification_ids)) - self.assertEqual(BUSINESS_MENU_IDS, set(verification_ids)) + self.assertEqual(len(LEGACY_BUSINESS_MENU_IDS), len(verification_ids)) + self.assertEqual(LEGACY_BUSINESS_MENU_IDS, set(verification_ids)) self.assertRegex(raw, r'查询结果必须为\s*64') normalized = [_normalized_sql_statement(statement) for statement in statements] @@ -2149,11 +2255,12 @@ class SqlContractTest(unittest.TestCase): self.assertEqual(('menu_id', *MYSQL_MENU_STABLE_COLUMNS), expected_columns) expected_rows = _insert_rows(statements[expected_inserts[0]], expected_table) - self.assertEqual(len(BUSINESS_MENU_IDS), len(expected_rows)) + self.assertEqual(len(LEGACY_BUSINESS_MENU_IDS), len(expected_rows)) expected_rows_by_id = {int(row[0]): tuple(row) for row in expected_rows} - self.assertEqual(BUSINESS_MENU_IDS, set(expected_rows_by_id)) + self.assertEqual(LEGACY_BUSINESS_MENU_IDS, set(expected_rows_by_id)) seed_rows_by_id = { - menu_id: tuple(row[: len(expected_columns)]) for menu_id, row in _business_menu_rows(raw).items() + menu_id: tuple(row[: len(expected_columns)]) + for menu_id, row in _business_menu_rows(raw, LEGACY_BUSINESS_MENU_IDS).items() } self.assertEqual(seed_rows_by_id, expected_rows_by_id) diff --git a/shierfeng-fastapi-frontend/scripts/verify-business-contract.mjs b/shierfeng-fastapi-frontend/scripts/verify-business-contract.mjs index 95d41ef..52aada0 100644 --- a/shierfeng-fastapi-frontend/scripts/verify-business-contract.mjs +++ b/shierfeng-fastapi-frontend/scripts/verify-business-contract.mjs @@ -17,21 +17,18 @@ const expectedRequests = { { url: "'/master/strain/' + strainId", method: 'get' }, { url: "'/master/strain'", method: 'post' }, { url: "'/master/strain'", method: 'put' }, - { url: "'/master/strain/' + strainIds", method: 'delete' } ], 'src/api/master/customer.js': [ { url: "'/master/customer/list'", method: 'get' }, { url: "'/master/customer/' + customerId", method: 'get' }, { url: "'/master/customer'", method: 'post' }, { url: "'/master/customer'", method: 'put' }, - { url: "'/master/customer/' + customerIds", method: 'delete' } ], 'src/api/master/site.js': [ { url: "'/master/site/list'", method: 'get' }, { url: "'/master/site/' + siteId", method: 'get' }, { url: "'/master/site'", method: 'post' }, { url: "'/master/site'", method: 'put' }, - { url: "'/master/site/' + siteIds", method: 'delete' }, { url: "'/master/site/' + siteId + '/users'", method: 'get' }, { url: "'/master/site/' + siteId + '/users'", method: 'put' } ], @@ -40,14 +37,12 @@ const expectedRequests = { { url: "'/master/line/' + lineId", method: 'get' }, { url: "'/master/line'", method: 'post' }, { url: "'/master/line'", method: 'put' }, - { url: "'/master/line/' + lineIds", method: 'delete' } ], 'src/api/master/warehouse.js': [ { url: "'/master/warehouse/list'", method: 'get' }, { url: "'/master/warehouse/' + warehouseId", method: 'get' }, { url: "'/master/warehouse'", method: 'post' }, { url: "'/master/warehouse'", method: 'put' }, - { url: "'/master/warehouse/' + warehouseIds", method: 'delete' }, { url: "'/master/warehouse/' + warehouseId + '/users'", method: 'get' }, { url: "'/master/warehouse/' + warehouseId + '/users'", method: 'put' } ], @@ -57,7 +52,6 @@ const expectedRequests = { { url: "'/master/pallet/' + palletId + '/qrcode'", method: 'get' }, { url: "'/master/pallet'", method: 'post' }, { url: "'/master/pallet'", method: 'put' }, - { url: "'/master/pallet/' + palletIds", method: 'delete' } ], 'src/api/business/inbound.js': [ { url: "'/business/inbound/list'", method: 'get' }, @@ -66,7 +60,6 @@ const expectedRequests = { { url: "'/business/inbound/' + inboundId", method: 'get' }, { url: "'/business/inbound'", method: 'post' }, { url: "'/business/inbound'", method: 'put' }, - { url: "'/business/inbound/' + inboundIds", method: 'delete' }, { url: "'/business/inbound/' + inboundId + '/confirm'", method: 'put' } ], 'src/api/business/filling.js': [ @@ -84,7 +77,6 @@ const expectedRequests = { { url: "'/business/shipment/' + shipmentId", method: 'get' }, { url: "'/business/shipment'", method: 'post' }, { url: "'/business/shipment'", method: 'put' }, - { url: "'/business/shipment/' + shipmentIds", method: 'delete' }, { url: "'/business/shipment/' + shipmentId + '/confirm'", method: 'put' } ], 'src/api/business/usage.js': [ @@ -134,43 +126,43 @@ const expectedReportExports = { const masterPageContracts = { strain: { relativePath: 'src/views/master/strain/index.vue', - apiNames: ['addStrain', 'delStrain', 'getStrain', 'listStrain', 'updateStrain'], - permissions: ['add', 'edit', 'list', 'query', 'remove'], + apiNames: ['addStrain', 'getStrain', 'listStrain', 'updateStrain'], + permissions: ['add', 'edit', 'list', 'query'], addPayloadKeys: ['remark', 'status', 'strainCode', 'strainName'], editPayloadKeys: ['remark', 'status', 'strainCode', 'strainId', 'strainName'] }, customer: { relativePath: 'src/views/master/customer/index.vue', - apiNames: ['addCustomer', 'delCustomer', 'getCustomer', 'listCustomer', 'updateCustomer'], - permissions: ['add', 'edit', 'list', 'query', 'remove'], + apiNames: ['addCustomer', 'getCustomer', 'listCustomer', 'updateCustomer'], + permissions: ['add', 'edit', 'list', 'query'], addPayloadKeys: ['customerCode', 'customerName', 'remark', 'status'], editPayloadKeys: ['customerCode', 'customerId', 'customerName', 'remark', 'status'] }, site: { relativePath: 'src/views/master/site/index.vue', - apiNames: ['addSite', 'delSite', 'getSite', 'listSite', 'updateSite'], - permissions: ['add', 'assign', 'edit', 'list', 'query', 'remove'], + apiNames: ['addSite', 'getSite', 'listSite', 'updateSite'], + permissions: ['add', 'assign', 'edit', 'list', 'query'], addPayloadKeys: ['address', 'customerId', 'remark', 'siteCode', 'siteName', 'status'], editPayloadKeys: ['address', 'customerId', 'remark', 'siteCode', 'siteId', 'siteName', 'status'] }, line: { relativePath: 'src/views/master/line/index.vue', - apiNames: ['addLine', 'delLine', 'getLine', 'listLine', 'updateLine'], - permissions: ['add', 'edit', 'list', 'query', 'remove'], + apiNames: ['addLine', 'getLine', 'listLine', 'updateLine'], + permissions: ['add', 'edit', 'list', 'query'], addPayloadKeys: ['lineCode', 'lineName', 'remark', 'siteId', 'status'], editPayloadKeys: ['lineCode', 'lineId', 'lineName', 'remark', 'siteId', 'status'] }, warehouse: { relativePath: 'src/views/master/warehouse/index.vue', - apiNames: ['addWarehouse', 'delWarehouse', 'getWarehouse', 'listWarehouse', 'updateWarehouse'], - permissions: ['add', 'assign', 'edit', 'list', 'query', 'remove'], + apiNames: ['addWarehouse', 'getWarehouse', 'listWarehouse', 'updateWarehouse'], + permissions: ['add', 'assign', 'edit', 'list', 'query'], addPayloadKeys: ['address', 'remark', 'status', 'warehouseCode', 'warehouseName'], editPayloadKeys: ['address', 'remark', 'status', 'warehouseCode', 'warehouseId', 'warehouseName'] }, pallet: { relativePath: 'src/views/master/pallet/index.vue', - apiNames: ['addPallet', 'delPallet', 'getPallet', 'getPalletQrcode', 'listPallet', 'updatePallet'], - permissions: ['add', 'edit', 'list', 'print', 'query', 'remove'], + apiNames: ['addPallet', 'getPallet', 'getPalletQrcode', 'listPallet', 'updatePallet'], + permissions: ['add', 'edit', 'list', 'print', 'query'], addPayloadKeys: ['capacityPackages', 'palletCode', 'remark'], editPayloadKeys: ['assetStatus', 'capacityPackages', 'palletId', 'remark'] } @@ -182,14 +174,13 @@ const businessPageContracts = { apiNames: [ 'addInbound', 'confirmInbound', - 'delInbound', 'getInbound', 'getInboundWarehouseOptions', 'listInbound', 'scanInboundPallet', 'updateInbound' ], - permissions: ['add', 'confirm', 'edit', 'list', 'query', 'remove'], + permissions: ['add', 'confirm', 'edit', 'list', 'query'], payloads: { addInbound: ['palletIds', 'warehouseId'], updateInbound: ['inboundId', 'palletIds', 'warehouseId'], @@ -217,7 +208,6 @@ const businessPageContracts = { apiNames: [ 'addShipment', 'confirmShipment', - 'delShipment', 'getShipment', 'getShipmentSiteOptions', 'getShipmentWarehouseOptions', @@ -225,7 +215,7 @@ const businessPageContracts = { 'scanShipmentPallet', 'updateShipment' ], - permissions: ['add', 'confirm', 'edit', 'list', 'query', 'remove'], + permissions: ['add', 'confirm', 'edit', 'list', 'query'], payloads: { addShipment: ['palletIds', 'sourceWarehouseId', 'targetSiteId'], updateShipment: ['palletIds', 'shipmentId', 'sourceWarehouseId', 'targetSiteId'], @@ -361,47 +351,40 @@ const expectedButtonPermissions = [ 'master:strain:query', 'master:strain:add', 'master:strain:edit', - 'master:strain:remove', 'master:customer:query', 'master:customer:add', 'master:customer:edit', - 'master:customer:remove', 'master:site:query', 'master:site:add', 'master:site:edit', - 'master:site:remove', 'master:site:assign', 'master:line:query', 'master:line:add', 'master:line:edit', - 'master:line:remove', 'master:warehouse:query', 'master:warehouse:add', 'master:warehouse:edit', - 'master:warehouse:remove', 'master:warehouse:assign', 'master:pallet:query', 'master:pallet:add', 'master:pallet:edit', - 'master:pallet:remove', 'master:pallet:print', 'business:inbound:query', 'business:inbound:add', 'business:inbound:edit', - 'business:inbound:remove', 'business:inbound:confirm', 'business:filling:query', 'business:filling:add', 'business:shipment:query', 'business:shipment:add', 'business:shipment:edit', - 'business:shipment:remove', 'business:shipment:confirm', 'business:usage:query', 'business:usage:add', 'business:return:query', 'business:return:request', - 'business:return:confirm' + 'business:return:confirm', + 'system:testbatch:adopt' ] const expectedPermissions = [...expectedPagePermissions, ...expectedButtonPermissions].sort() @@ -536,7 +519,7 @@ function parseStaticPermissionArray(expression) { assert.ok(index === ranges.length - 1, '权限门禁数组禁止稀疏元素,仅允许单个尾逗号') continue } - const literal = /^(['"])((?:master|business|report):[a-z]+:[a-z]+)\1$/.exec(item) + const literal = /^(['"])((?:master|business|report|system):[a-z]+:[a-z]+)\1$/.exec(item) assert.ok(literal, '权限门禁数组禁止动态表达式或非法字面量') permissions.push(literal[2]) } @@ -1147,8 +1130,8 @@ test('窄屏模式控件仍保留正式退出与测试批次切换入口', async test('17 个页面的真实权限门禁逐项匹配独立硬编码契约', async () => { assert.equal(expectedPagePermissions.length, 17) - assert.equal(expectedButtonPermissions.length, 44) - assert.equal(new Set(expectedPermissions).size, 61) + assert.equal(expectedButtonPermissions.length, 37) + assert.equal(new Set(expectedPermissions).size, 54) const relativePaths = [ ...Object.values(masterPageContracts), ...Object.values(businessPageContracts), @@ -1450,7 +1433,7 @@ test('首次入库确认失败后取消重试不丢弃原幂等键', async () => test('17 个 API 文件逐一匹配硬编码请求契约', async (t) => { assert.equal(Object.keys(expectedRequests).length, 17) - assert.equal(Object.values(expectedRequests).flat().length, 74) + assert.equal(Object.values(expectedRequests).flat().length, 66) for (const [relativePath, expected] of Object.entries(expectedRequests)) { await t.test(relativePath, async () => { @@ -1647,7 +1630,6 @@ test('六个基础资料页面逐页满足 API、权限、分页和安全删除 assertButtonsUsePermission(source, 'submitCreate', `${permissionPrefix}:add`) assertButtonsUsePermission(source, 'handleUpdate', `${permissionPrefix}:edit`) assertButtonsUsePermission(source, 'submitUpdate', `${permissionPrefix}:edit`) - assertButtonsUsePermission(source, 'handleDelete', `${permissionPrefix}:remove`) for (const button of extractButtonBlocks(source, 'handleUpdate')) { assert.match( @@ -1708,20 +1690,10 @@ test('六个基础资料页面逐页满足 API、权限、分页和安全删除 assert.doesNotMatch(selectionColumn, /:selectable=/) assert.doesNotMatch(source, /function isRowSelectable\(/) assert.match(source, /const selectedIds = ref\(\[\]\)/) - assert.match(source, /const removableIds = ref\(\[\]\)/) const selectionSource = source.slice(source.indexOf('function handleSelectionChange('), source.indexOf('function handleAdd(')) assert.match(selectionSource, new RegExp(`selectedIds\\.value = selection\\.map\\(\\(item\\) => item\\.${idKey}\\)`)) - assert.match(selectionSource, new RegExp(`removableIds\\.value = selection\\s*\\.filter\\(\\(item\\) => item\\.canRemove === true\\)\\s*\\.map\\(\\(item\\) => item\\.${idKey}\\)`)) assert.match(selectionSource, /single\.value = selectedIds\.value\.length !== 1/) - assert.match(selectionSource, /multiple\.value = removableIds\.value\.length === 0/) assert.match(detailSource, new RegExp(`const ${idKey} = row\\.${idKey} \\|\\| selectedIds\\.value\\[0\\]`)) - assert.match(source, new RegExp(`const targetIds = row\\?\\.${idKey} \\? \\(row\\.canRemove === true \\? \\[row\\.${idKey}\\] : \\[\\]\\) : removableIds\\.value`)) - const rowDeleteButtons = extractButtonBlocks(source, 'handleDelete') - .filter((button) => button.includes('handleDelete(scope.row)')) - assert.ok(rowDeleteButtons.length > 0, `${resource} 缺少行删除按钮`) - for (const button of rowDeleteButtons) { - assert.match(button, /v-if="scope\.row\.canRemove === true"/) - } const rowUpdateButtons = extractButtonBlocks(source, 'handleUpdate') .filter((button) => button.includes('handleUpdate(scope.row)')) assert.ok(rowUpdateButtons.length > 0, `${resource} 缺少行修改按钮`) @@ -1980,7 +1952,6 @@ test('入库页面只允许 DRAFT 变更并以 authoritative 详情和独立确 assertButtonsUsePermission(source, 'handleAdd', 'business:inbound:add') assertButtonsUsePermission(source, 'submitInbound', 'business:inbound:add') assertButtonsUsePermission(source, 'handleUpdate', 'business:inbound:edit') - assertButtonsUsePermission(source, 'handleDelete', 'business:inbound:remove') assertButtonsUsePermission(source, 'handleConfirm', 'business:inbound:confirm') assert.match(source, /proxy\.\$auth\.hasPermiAnd\(\['business:inbound:query', 'business:inbound:add'\]\)/) assert.match(source, /proxy\.\$auth\.hasPermiAnd\(\['business:inbound:query', 'business:inbound:edit'\]\)/) @@ -1999,9 +1970,8 @@ test('入库页面只允许 DRAFT 变更并以 authoritative 详情和独立确 /^function handleAdd\(\)\s*\{\s*if \(!canAddInbound\.value \|\| operationPending\.value \|\| optionLoading\.value \|\| detailLoading\.value \|\| open\.value\) return/ ) assert.match(source, /scope\.row\.status === ['"]DRAFT['"][\s\S]*?handleUpdate\(scope\.row\)/) - assert.match(source, /scope\.row\.status === ['"]DRAFT['"][\s\S]*?handleDelete\(scope\.row\)/) assert.match(source, /scope\.row\.status === ['"]DRAFT['"][\s\S]*?handleConfirm\(scope\.row\)/) - for (const functionName of ['handleUpdate', 'handleDelete', 'handleConfirm']) { + for (const functionName of ['handleUpdate', 'handleConfirm']) { assert.match(extractNamedFunction(source, functionName), /status !== ['"]DRAFT['"]/) } @@ -2041,11 +2011,6 @@ test('入库页面只允许 DRAFT 变更并以 authoritative 详情和独立确 ) assert.match(submitSource, /if \(!valid\) \{\s*submitLoading\.value = false\s*return/) assert.match(submitSource, /\.finally\(\(\) => \{\s*submitLoading\.value = false/) - const deleteSource = extractNamedFunction(source, 'handleDelete') - assert.ok( - deleteSource.indexOf('deleteLoading.value = true') < deleteSource.indexOf('proxy.$modal.confirm('), - '入库删除必须在打开二次确认框前加锁' - ) assert.deepEqual(extractSecondCallObjectKeys(source, 'scanInboundPallet'), ['warehouseId']) const scanSource = extractNamedFunction(source, 'handleInboundScan') @@ -2187,7 +2152,6 @@ test('发货页面只允许 DRAFT 变更、展示只读余量并复用确认幂 assertButtonsUsePermission(source, 'handleAdd', 'business:shipment:add') assertButtonsUsePermission(source, 'submitShipment', 'business:shipment:add') assertButtonsUsePermission(source, 'handleUpdate', 'business:shipment:edit') - assertButtonsUsePermission(source, 'handleDelete', 'business:shipment:remove') assertButtonsUsePermission(source, 'handleConfirm', 'business:shipment:confirm') assert.match(source, /proxy\.\$auth\.hasPermiAnd\(\['business:shipment:query', 'business:shipment:add'\]\)/) assert.match(source, /proxy\.\$auth\.hasPermiAnd\(\['business:shipment:query', 'business:shipment:edit'\]\)/) @@ -2206,9 +2170,8 @@ test('发货页面只允许 DRAFT 变更、展示只读余量并复用确认幂 /^function handleAdd\(\)\s*\{\s*if \(!canAddShipment\.value \|\| operationPending\.value \|\| optionLoading\.value \|\| detailLoading\.value \|\| open\.value\) return/ ) assert.match(source, /scope\.row\.status === ['"]DRAFT['"][\s\S]*?handleUpdate\(scope\.row\)/) - assert.match(source, /scope\.row\.status === ['"]DRAFT['"][\s\S]*?handleDelete\(scope\.row\)/) assert.match(source, /scope\.row\.status === ['"]DRAFT['"][\s\S]*?handleConfirm\(scope\.row\)/) - for (const functionName of ['handleUpdate', 'handleDelete', 'handleConfirm']) { + for (const functionName of ['handleUpdate', 'handleConfirm']) { assert.match(extractNamedFunction(source, functionName), /status !== ['"]DRAFT['"]/) } @@ -2250,11 +2213,6 @@ test('发货页面只允许 DRAFT 变更、展示只读余量并复用确认幂 ) assert.match(submitSource, /if \(!valid\) \{\s*submitLoading\.value = false\s*return/) assert.match(submitSource, /\.finally\(\(\) => \{\s*submitLoading\.value = false/) - const deleteSource = extractNamedFunction(source, 'handleDelete') - assert.ok( - deleteSource.indexOf('deleteLoading.value = true') < deleteSource.indexOf('proxy.$modal.confirm('), - '发货删除必须在打开二次确认框前加锁' - ) assert.deepEqual(extractSecondCallObjectKeys(source, 'scanShipmentPallet'), ['sourceWarehouseId', 'targetSiteId']) assert.match(source, /label="[^"]*只读[^"]*"[^>]*prop="remainingPackages"|prop="remainingPackages"[\s\S]*?不可修改/) @@ -2534,6 +2492,7 @@ test('测试批次管理与旧数据纳入遵守 root、权限和预览提交契 assert.match(source, /useUserStore\(\)/, `${rootType} 必须读取 admin 可见性`) assert.match(source, /useTestBatchStore\(\)/, `${rootType} 必须读取测试模式`) assert.match(source, /userStore\.admin === true\s*&&\s*!testBatchStore\.isTestMode/, `${rootType} 纳入入口必须只在管理员正式模式显示`) + assert.match(source, /v-hasPermi="\['system:testbatch:adopt'\]"/, `${rootType} 纳入入口必须校验批次纳入权限`) assert.match(source, new RegExp(`\\s*item\\.${contract.id}\\)`), `${rootType} 必须传递正整数根 ID`) } diff --git a/shierfeng-fastapi-frontend/src/api/business/inbound.js b/shierfeng-fastapi-frontend/src/api/business/inbound.js index 8a8a4ee..cbae670 100644 --- a/shierfeng-fastapi-frontend/src/api/business/inbound.js +++ b/shierfeng-fastapi-frontend/src/api/business/inbound.js @@ -52,14 +52,6 @@ export function updateInbound(data) { }) } -// 删除首次入库草稿 -export function delInbound(inboundIds) { - return request({ - url: '/business/inbound/' + inboundIds, - method: 'delete' - }) -} - // 确认首次入库单 export function confirmInbound(inboundId, data) { return request({ diff --git a/shierfeng-fastapi-frontend/src/api/business/shipment.js b/shierfeng-fastapi-frontend/src/api/business/shipment.js index c7e0505..cf9fa15 100644 --- a/shierfeng-fastapi-frontend/src/api/business/shipment.js +++ b/shierfeng-fastapi-frontend/src/api/business/shipment.js @@ -60,14 +60,6 @@ export function updateShipment(data) { }) } -// 删除整托发货草稿 -export function delShipment(shipmentIds) { - return request({ - url: '/business/shipment/' + shipmentIds, - method: 'delete' - }) -} - // 确认整托发货单 export function confirmShipment(shipmentId, data) { return request({ diff --git a/shierfeng-fastapi-frontend/src/api/master/customer.js b/shierfeng-fastapi-frontend/src/api/master/customer.js index 5e46fbb..b96fa7c 100644 --- a/shierfeng-fastapi-frontend/src/api/master/customer.js +++ b/shierfeng-fastapi-frontend/src/api/master/customer.js @@ -34,11 +34,3 @@ export function updateCustomer(data) { data: data }) } - -// 删除客户 -export function delCustomer(customerIds) { - return request({ - url: '/master/customer/' + customerIds, - method: 'delete' - }) -} diff --git a/shierfeng-fastapi-frontend/src/api/master/line.js b/shierfeng-fastapi-frontend/src/api/master/line.js index 5d98b33..73bd06e 100644 --- a/shierfeng-fastapi-frontend/src/api/master/line.js +++ b/shierfeng-fastapi-frontend/src/api/master/line.js @@ -34,11 +34,3 @@ export function updateLine(data) { data: data }) } - -// 删除生产线 -export function delLine(lineIds) { - return request({ - url: '/master/line/' + lineIds, - method: 'delete' - }) -} diff --git a/shierfeng-fastapi-frontend/src/api/master/pallet.js b/shierfeng-fastapi-frontend/src/api/master/pallet.js index 4a398b3..95bc4fa 100644 --- a/shierfeng-fastapi-frontend/src/api/master/pallet.js +++ b/shierfeng-fastapi-frontend/src/api/master/pallet.js @@ -43,11 +43,3 @@ export function updatePallet(data) { data: data }) } - -// 删除托盘 -export function delPallet(palletIds) { - return request({ - url: '/master/pallet/' + palletIds, - method: 'delete' - }) -} diff --git a/shierfeng-fastapi-frontend/src/api/master/site.js b/shierfeng-fastapi-frontend/src/api/master/site.js index 82fbe8b..bce1254 100644 --- a/shierfeng-fastapi-frontend/src/api/master/site.js +++ b/shierfeng-fastapi-frontend/src/api/master/site.js @@ -35,14 +35,6 @@ export function updateSite(data) { }) } -// 删除客户现场 -export function delSite(siteIds) { - return request({ - url: '/master/site/' + siteIds, - method: 'delete' - }) -} - // 查询客户现场可分配用户 export function getSiteUsers(siteId) { return request({ diff --git a/shierfeng-fastapi-frontend/src/api/master/strain.js b/shierfeng-fastapi-frontend/src/api/master/strain.js index b5a3c61..5bb9c7b 100644 --- a/shierfeng-fastapi-frontend/src/api/master/strain.js +++ b/shierfeng-fastapi-frontend/src/api/master/strain.js @@ -34,11 +34,3 @@ export function updateStrain(data) { data: data }) } - -// 删除菌种 -export function delStrain(strainIds) { - return request({ - url: '/master/strain/' + strainIds, - method: 'delete' - }) -} diff --git a/shierfeng-fastapi-frontend/src/api/master/warehouse.js b/shierfeng-fastapi-frontend/src/api/master/warehouse.js index e311e3f..fb25a12 100644 --- a/shierfeng-fastapi-frontend/src/api/master/warehouse.js +++ b/shierfeng-fastapi-frontend/src/api/master/warehouse.js @@ -35,14 +35,6 @@ export function updateWarehouse(data) { }) } -// 删除仓库 -export function delWarehouse(warehouseIds) { - return request({ - url: '/master/warehouse/' + warehouseIds, - method: 'delete' - }) -} - // 查询仓库可分配用户 export function getWarehouseUsers(warehouseId) { return request({ diff --git a/shierfeng-fastapi-frontend/src/views/business/filling/index.vue b/shierfeng-fastapi-frontend/src/views/business/filling/index.vue index 78feff1..a307241 100644 --- a/shierfeng-fastapi-frontend/src/views/business/filling/index.vue +++ b/shierfeng-fastapi-frontend/src/views/business/filling/index.vue @@ -32,7 +32,7 @@ -
纳入测试批次
+
纳入测试批次
diff --git a/shierfeng-fastapi-frontend/src/views/business/inbound/index.vue b/shierfeng-fastapi-frontend/src/views/business/inbound/index.vue index 61670c8..42d9c9e 100644 --- a/shierfeng-fastapi-frontend/src/views/business/inbound/index.vue +++ b/shierfeng-fastapi-frontend/src/views/business/inbound/index.vue @@ -41,7 +41,7 @@ -
纳入测试批次
+
纳入测试批次
@@ -72,15 +72,6 @@ @click="handleUpdate(scope.row)" v-hasPermi="['business:inbound:edit']" >编辑 - 删除 Boolean(form.value.inboundId)) const canAddInbound = computed(() => proxy.$auth.hasPermiAnd(['business:inbound:query', 'business:inbound:add'])) const canEditInbound = computed(() => proxy.$auth.hasPermiAnd(['business:inbound:query', 'business:inbound:edit'])) -const operationPending = computed(() => submitLoading.value || deleteLoading.value || confirmLoading.value) +const operationPending = computed(() => submitLoading.value || confirmLoading.value) let listGeneration = 0 let optionGeneration = 0 let detailGeneration = 0 @@ -545,27 +534,6 @@ function saveInboundUpdate() { submitInboundForm(true) } -function handleDelete(row) { - if (row.status !== 'DRAFT') { - proxy.$modal.msgWarning('只有草稿入库单可以删除') - return - } - if (operationPending.value) return - deleteLoading.value = true - proxy.$modal.confirm(`是否确认删除入库单“${row.inboundNo || `#${row.inboundId}`}”?`).then(() => { - return delInbound(String(row.inboundId)) - }).then((response) => { - proxy.$modal.msgSuccess(response.msg || '删除成功') - getList() - }).catch((error) => { - if (error !== 'cancel' && error !== 'close') { - loadError.value = '入库单删除失败,请稍后重试' - } - }).finally(() => { - deleteLoading.value = false - }) -} - function clearConfirmIdempotency() { confirmIdempotency.inboundId = undefined confirmIdempotency.key = '' diff --git a/shierfeng-fastapi-frontend/src/views/business/return/index.vue b/shierfeng-fastapi-frontend/src/views/business/return/index.vue index 6a539de..a3e665a 100644 --- a/shierfeng-fastapi-frontend/src/views/business/return/index.vue +++ b/shierfeng-fastapi-frontend/src/views/business/return/index.vue @@ -173,7 +173,7 @@ -
纳入测试批次
+
纳入测试批次
diff --git a/shierfeng-fastapi-frontend/src/views/business/shipment/index.vue b/shierfeng-fastapi-frontend/src/views/business/shipment/index.vue index bf10a69..cfb7393 100644 --- a/shierfeng-fastapi-frontend/src/views/business/shipment/index.vue +++ b/shierfeng-fastapi-frontend/src/views/business/shipment/index.vue @@ -38,7 +38,7 @@ -
纳入测试批次
+
纳入测试批次
@@ -69,15 +69,6 @@ @click="handleUpdate(scope.row)" v-hasPermi="['business:shipment:edit']" >编辑
- 删除 Boolean(form.value.shipmentId)) const canAddShipment = computed(() => proxy.$auth.hasPermiAnd(['business:shipment:query', 'business:shipment:add'])) const canEditShipment = computed(() => proxy.$auth.hasPermiAnd(['business:shipment:query', 'business:shipment:edit'])) -const operationPending = computed(() => submitLoading.value || deleteLoading.value || confirmLoading.value) +const operationPending = computed(() => submitLoading.value || confirmLoading.value) let listGeneration = 0 let optionGeneration = 0 let detailGeneration = 0 @@ -577,25 +566,6 @@ function saveShipmentUpdate() { submitShipmentForm(true) } -function handleDelete(row) { - if (row.status !== 'DRAFT') { - proxy.$modal.msgWarning('只有草稿发货单可以删除') - return - } - if (operationPending.value) return - deleteLoading.value = true - proxy.$modal.confirm(`是否确认删除发货单“${row.shipmentNo || `#${row.shipmentId}`}”?`).then(() => { - return delShipment(String(row.shipmentId)) - }).then((response) => { - proxy.$modal.msgSuccess(response.msg || '删除成功') - getList() - }).catch((error) => { - if (error !== 'cancel' && error !== 'close') loadError.value = '发货单删除失败,请稍后重试' - }).finally(() => { - deleteLoading.value = false - }) -} - function clearConfirmIdempotency() { confirmIdempotency.shipmentId = undefined confirmIdempotency.key = '' diff --git a/shierfeng-fastapi-frontend/src/views/business/usage/index.vue b/shierfeng-fastapi-frontend/src/views/business/usage/index.vue index d2cac56..1f9915c 100644 --- a/shierfeng-fastapi-frontend/src/views/business/usage/index.vue +++ b/shierfeng-fastapi-frontend/src/views/business/usage/index.vue @@ -112,7 +112,7 @@ -
纳入测试批次
+
纳入测试批次
diff --git a/shierfeng-fastapi-frontend/src/views/master/customer/index.vue b/shierfeng-fastapi-frontend/src/views/master/customer/index.vue index ec1b512..6a1fa21 100644 --- a/shierfeng-fastapi-frontend/src/views/master/customer/index.vue +++ b/shierfeng-fastapi-frontend/src/views/master/customer/index.vue @@ -27,10 +27,9 @@ @click="handleUpdate()" v-hasPermi="['master:customer:edit']" >修改
- 删除 - 纳入测试批次 + 纳入测试批次 @@ -51,10 +50,6 @@ v-if="proxy.$auth.hasPermiAnd(['master:customer:query', 'master:customer:edit'])" link type="primary" icon="Edit" :disabled="detailLoading" @click="handleUpdate(scope.row)" v-hasPermi="['master:customer:edit']" >修改 - 删除
@@ -117,7 +112,7 @@ diff --git a/shierfeng-fastapi-frontend/src/views/master/line/index.vue b/shierfeng-fastapi-frontend/src/views/master/line/index.vue index 3912164..7a4ab4f 100644 --- a/shierfeng-fastapi-frontend/src/views/master/line/index.vue +++ b/shierfeng-fastapi-frontend/src/views/master/line/index.vue @@ -30,10 +30,9 @@ @click="handleUpdate()" v-hasPermi="['master:line:edit']" >修改 - 删除 - 纳入测试批次 + 纳入测试批次 @@ -55,10 +54,6 @@ v-if="proxy.$auth.hasPermiAnd(['master:line:query', 'master:line:edit'])" link type="primary" icon="Edit" :disabled="detailLoading" @click="handleUpdate(scope.row)" v-hasPermi="['master:line:edit']" >修改 - 删除
@@ -119,7 +114,7 @@ diff --git a/shierfeng-fastapi-frontend/src/views/master/pallet/index.vue b/shierfeng-fastapi-frontend/src/views/master/pallet/index.vue index 55be8b6..a72d847 100644 --- a/shierfeng-fastapi-frontend/src/views/master/pallet/index.vue +++ b/shierfeng-fastapi-frontend/src/views/master/pallet/index.vue @@ -24,10 +24,9 @@ @click="handleUpdate()" v-hasPermi="['master:pallet:edit']" >修改 - 删除 - 纳入测试批次 + 纳入测试批次 @@ -52,10 +51,6 @@ v-if="proxy.$auth.hasPermiAnd(['master:pallet:query', 'master:pallet:edit'])" link type="primary" icon="Edit" :disabled="detailLoading" @click="handleUpdate(scope.row)" v-hasPermi="['master:pallet:edit']" >修改 - 删除 @@ -119,7 +114,7 @@ diff --git a/shierfeng-fastapi-frontend/src/views/master/strain/index.vue b/shierfeng-fastapi-frontend/src/views/master/strain/index.vue index 19bfe90..6ef59fd 100644 --- a/shierfeng-fastapi-frontend/src/views/master/strain/index.vue +++ b/shierfeng-fastapi-frontend/src/views/master/strain/index.vue @@ -34,12 +34,9 @@ v-hasPermi="['master:strain:edit']" >修改 - - 删除 - - 纳入测试批次 + 纳入测试批次 @@ -68,14 +65,6 @@ @click="handleUpdate(scope.row)" v-hasPermi="['master:strain:edit']" >修改 - 删除 @@ -136,7 +125,7 @@ diff --git a/shierfeng-fastapi-frontend/src/views/master/warehouse/index.vue b/shierfeng-fastapi-frontend/src/views/master/warehouse/index.vue index 881ae8c..59643a3 100644 --- a/shierfeng-fastapi-frontend/src/views/master/warehouse/index.vue +++ b/shierfeng-fastapi-frontend/src/views/master/warehouse/index.vue @@ -27,11 +27,10 @@ @click="handleUpdate()" v-hasPermi="['master:warehouse:edit']" >修改 - 删除 分配用户 - 纳入测试批次 + 纳入测试批次 @@ -54,10 +53,6 @@ v-if="proxy.$auth.hasPermiAnd(['master:warehouse:query', 'master:warehouse:edit'])" link type="primary" icon="Edit" :disabled="detailLoading" @click="handleUpdate(scope.row)" v-hasPermi="['master:warehouse:edit']" >修改 - 删除 @@ -121,7 +116,7 @@