权限:移除业务物理删除入口

This commit is contained in:
焦龙言
2026-09-24 08:13:16 +08:00
parent 4706c4afeb
commit ae8c4c8c38
35 changed files with 668 additions and 445 deletions
@@ -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(
@@ -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(
@@ -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(
@@ -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(
@@ -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(
@@ -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(
@@ -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(
@@ -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)
@@ -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(
@@ -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;
@@ -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;
@@ -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, '');
@@ -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, '');
-- ----------------------------
@@ -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):
@@ -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||<NULL>
122|0|业务管理|businessOperation||2||<NULL>
123|0|报表查询|businessReport||3||<NULL>
@@ -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)
@@ -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(`<test-batch-adoption-dialog\\b[\\s\\S]*?:root-type="['\"]${rootType}['\"][\\s\\S]*?:resource-ids="selectedIds"`))
assert.match(source, new RegExp(`selectedIds\\.value\\s*=\\s*selection\\.map\\(\\(item\\)\\s*=>\\s*item\\.${contract.id}\\)`), `${rootType} 必须传递正整数根 ID`)
}
@@ -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({
@@ -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({
@@ -34,11 +34,3 @@ export function updateCustomer(data) {
data: data
})
}
// 删除客户
export function delCustomer(customerIds) {
return request({
url: '/master/customer/' + customerIds,
method: 'delete'
})
}
@@ -34,11 +34,3 @@ export function updateLine(data) {
data: data
})
}
// 删除生产线
export function delLine(lineIds) {
return request({
url: '/master/line/' + lineIds,
method: 'delete'
})
}
@@ -43,11 +43,3 @@ export function updatePallet(data) {
data: data
})
}
// 删除托盘
export function delPallet(palletIds) {
return request({
url: '/master/pallet/' + palletIds,
method: 'delete'
})
}
@@ -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({
@@ -34,11 +34,3 @@ export function updateStrain(data) {
data: data
})
}
// 删除菌种
export function delStrain(strainIds) {
return request({
url: '/master/strain/' + strainIds,
method: 'delete'
})
}
@@ -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({
@@ -32,7 +32,7 @@
</el-row>
<el-alert v-if="loadError" :title="loadError" type="error" :closable="false" show-icon class="mb8" />
<div v-if="userStore.admin === true && !testBatchStore.isTestMode" class="mb8"><el-button type="warning" plain icon="FolderAdd" :disabled="!selectedIds.length || operationPending" @click="openAdoptionDialog">纳入测试批次</el-button></div>
<div v-if="userStore.admin === true && !testBatchStore.isTestMode" class="mb8"><el-button type="warning" plain icon="FolderAdd" :disabled="!selectedIds.length || operationPending" @click="openAdoptionDialog" v-hasPermi="['system:testbatch:adopt']">纳入测试批次</el-button></div>
<el-table v-loading="loading" empty-text="暂无数据" :data="fillingList" class="data-table" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column label="装填单号" prop="fillingNo" min-width="170" :show-overflow-tooltip="true" />
@@ -41,7 +41,7 @@
</el-row>
<el-alert v-if="loadError" :title="loadError" type="error" :closable="false" show-icon class="mb8" />
<div v-if="userStore.admin === true && !testBatchStore.isTestMode" class="mb8"><el-button type="warning" plain icon="FolderAdd" :disabled="!selectedIds.length || operationPending" @click="openAdoptionDialog">纳入测试批次</el-button></div>
<div v-if="userStore.admin === true && !testBatchStore.isTestMode" class="mb8"><el-button type="warning" plain icon="FolderAdd" :disabled="!selectedIds.length || operationPending" @click="openAdoptionDialog" v-hasPermi="['system:testbatch:adopt']">纳入测试批次</el-button></div>
<el-table v-loading="loading" empty-text="暂无数据" :data="inboundList" class="data-table" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column label="入库单号" prop="inboundNo" min-width="170" :show-overflow-tooltip="true" />
@@ -72,15 +72,6 @@
@click="handleUpdate(scope.row)"
v-hasPermi="['business:inbound:edit']"
>编辑</el-button>
<el-button
v-if="scope.row.status === 'DRAFT'"
link
type="danger"
icon="Delete"
:disabled="operationPending"
@click="handleDelete(scope.row)"
v-hasPermi="['business:inbound:remove']"
>删除</el-button>
<el-button
v-if="scope.row.status === 'DRAFT'"
link
@@ -208,7 +199,6 @@ import useTestBatchStore from '@/store/modules/testBatch'
import {
addInbound,
confirmInbound,
delInbound,
getInbound,
getInboundWarehouseOptions,
listInbound,
@@ -229,7 +219,6 @@ const optionLoading = ref(false)
const detailLoading = ref(false)
const scanLoading = ref(false)
const submitLoading = ref(false)
const deleteLoading = ref(false)
const confirmLoading = ref(false)
const loadError = ref('')
const optionError = ref('')
@@ -242,7 +231,7 @@ const editDetailReady = ref(false)
const isEdit = computed(() => 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 = ''
@@ -173,7 +173,7 @@
</el-form>
<el-alert v-if="loadError" :title="loadError" type="error" :closable="false" show-icon class="mb8" />
<div v-if="userStore.admin === true && !testBatchStore.isTestMode" class="mb8"><el-button type="warning" plain icon="FolderAdd" :disabled="!selectedIds.length || operationPending" @click="openAdoptionDialog">纳入测试批次</el-button></div>
<div v-if="userStore.admin === true && !testBatchStore.isTestMode" class="mb8"><el-button type="warning" plain icon="FolderAdd" :disabled="!selectedIds.length || operationPending" @click="openAdoptionDialog" v-hasPermi="['system:testbatch:adopt']">纳入测试批次</el-button></div>
<el-table v-loading="loading" empty-text="暂无数据" :data="returnList" class="data-table" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column label="退回单号" prop="returnNo" min-width="170" :show-overflow-tooltip="true" />
@@ -38,7 +38,7 @@
</el-row>
<el-alert v-if="loadError" :title="loadError" type="error" :closable="false" show-icon class="mb8" />
<div v-if="userStore.admin === true && !testBatchStore.isTestMode" class="mb8"><el-button type="warning" plain icon="FolderAdd" :disabled="!selectedIds.length || operationPending" @click="openAdoptionDialog">纳入测试批次</el-button></div>
<div v-if="userStore.admin === true && !testBatchStore.isTestMode" class="mb8"><el-button type="warning" plain icon="FolderAdd" :disabled="!selectedIds.length || operationPending" @click="openAdoptionDialog" v-hasPermi="['system:testbatch:adopt']">纳入测试批次</el-button></div>
<el-table v-loading="loading" empty-text="暂无数据" :data="shipmentList" class="data-table" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column label="发货单号" prop="shipmentNo" min-width="170" :show-overflow-tooltip="true" />
@@ -69,15 +69,6 @@
@click="handleUpdate(scope.row)"
v-hasPermi="['business:shipment:edit']"
>编辑</el-button>
<el-button
v-if="scope.row.status === 'DRAFT'"
link
type="danger"
icon="Delete"
:disabled="operationPending"
@click="handleDelete(scope.row)"
v-hasPermi="['business:shipment:remove']"
>删除</el-button>
<el-button
v-if="scope.row.status === 'DRAFT'"
link
@@ -219,7 +210,6 @@ import useTestBatchStore from '@/store/modules/testBatch'
import {
addShipment,
confirmShipment,
delShipment,
getShipment,
getShipmentSiteOptions,
getShipmentWarehouseOptions,
@@ -242,7 +232,6 @@ const optionLoading = ref(false)
const detailLoading = ref(false)
const scanLoading = ref(false)
const submitLoading = ref(false)
const deleteLoading = ref(false)
const confirmLoading = ref(false)
const loadError = ref('')
const optionError = ref('')
@@ -255,7 +244,7 @@ const editDetailReady = ref(false)
const isEdit = computed(() => 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 = ''
@@ -112,7 +112,7 @@
</el-form>
<el-alert v-if="loadError" :title="loadError" type="error" :closable="false" show-icon class="mb8" />
<div v-if="userStore.admin === true && !testBatchStore.isTestMode" class="mb8"><el-button type="warning" plain icon="FolderAdd" :disabled="!selectedIds.length || operationPending" @click="openAdoptionDialog">纳入测试批次</el-button></div>
<div v-if="userStore.admin === true && !testBatchStore.isTestMode" class="mb8"><el-button type="warning" plain icon="FolderAdd" :disabled="!selectedIds.length || operationPending" @click="openAdoptionDialog" v-hasPermi="['system:testbatch:adopt']">纳入测试批次</el-button></div>
<el-table v-loading="loading" empty-text="暂无数据" :data="usageList" class="data-table" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column label="使用记录号" prop="usageNo" min-width="170" :show-overflow-tooltip="true" />
@@ -27,10 +27,9 @@
@click="handleUpdate()" v-hasPermi="['master:customer:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5"><el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['master:customer:remove']">删除</el-button></el-col>
</el-row>
<el-row v-if="userStore.admin === true && !testBatchStore.isTestMode" :gutter="10" class="mb8 master-toolbar">
<el-col :span="1.5"><el-button type="warning" plain icon="FolderAdd" :disabled="!selectedIds.length" @click="openAdoptionDialog">纳入测试批次</el-button></el-col>
<el-col :span="1.5"><el-button type="warning" plain icon="FolderAdd" :disabled="!selectedIds.length" @click="openAdoptionDialog" v-hasPermi="['system:testbatch:adopt']">纳入测试批次</el-button></el-col>
</el-row>
<el-alert v-if="loadError" :title="loadError" type="error" :closable="false" show-icon class="mb8" />
@@ -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']"
>修改</el-button>
<el-button
v-if="scope.row.canRemove === true"
link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['master:customer:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
@@ -117,7 +112,7 @@
</style>
<script setup name="Customer">
import { addCustomer, delCustomer, getCustomer, listCustomer, updateCustomer } from "@/api/master/customer";
import { addCustomer, getCustomer, listCustomer, updateCustomer } from "@/api/master/customer";
import TestBatchAdoptionDialog from '@/components/TestBatchAdoptionDialog'
import useUserStore from '@/store/modules/user'
import useTestBatchStore from '@/store/modules/testBatch'
@@ -130,7 +125,6 @@ const adoptionDialogRef = ref()
const { sys_normal_disable } = proxy.useDict("sys_normal_disable");
const customerList = ref([]);
const selectedIds = ref([]);
const removableIds = ref([]);
const loading = ref(false);
const detailLoading = ref(false);
const submitLoading = ref(false);
@@ -138,7 +132,6 @@ const loadError = ref("");
const formError = ref("");
const open = ref(false);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref("");
let listGeneration = 0;
@@ -223,11 +216,7 @@ function handleQuery() { queryParams.value.pageNum = 1; getList(); }
function resetQuery() { proxy.resetForm("queryRef"); handleQuery(); }
function handleSelectionChange(selection) {
selectedIds.value = selection.map((item) => item.customerId);
removableIds.value = selection
.filter((item) => item.canRemove === true)
.map((item) => item.customerId);
single.value = selectedIds.value.length !== 1;
multiple.value = removableIds.value.length === 0;
}
function openAdoptionDialog() { adoptionDialogRef.value?.open() }
@@ -309,15 +298,5 @@ function submitUpdate() {
}).finally(() => { submitLoading.value = false; });
});
}
function handleDelete(row) {
const targetIds = row?.customerId ? (row.canRemove === true ? [row.customerId] : []) : removableIds.value;
if (!targetIds.length) { proxy.$modal.msgWarning("请选择允许删除的客户"); return; }
proxy.$modal.confirm(`是否确认删除客户编号为“${targetIds.join(",")}”的数据项?`).then(() =>
delCustomer(targetIds.join(","))
).then(() => { proxy.$modal.msgSuccess("删除成功"); getList(); }).catch((error) => {
if (error !== "cancel" && error !== "close") loadError.value = "客户删除失败,请稍后重试";
});
}
getList();
</script>
@@ -30,10 +30,9 @@
@click="handleUpdate()" v-hasPermi="['master:line:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5"><el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['master:line:remove']">删除</el-button></el-col>
</el-row>
<el-row v-if="userStore.admin === true && !testBatchStore.isTestMode" :gutter="10" class="mb8 master-toolbar">
<el-col :span="1.5"><el-button type="warning" plain icon="FolderAdd" :disabled="!selectedIds.length" @click="openAdoptionDialog">纳入测试批次</el-button></el-col>
<el-col :span="1.5"><el-button type="warning" plain icon="FolderAdd" :disabled="!selectedIds.length" @click="openAdoptionDialog" v-hasPermi="['system:testbatch:adopt']">纳入测试批次</el-button></el-col>
</el-row>
<el-alert v-if="loadError" :title="loadError" type="error" :closable="false" show-icon class="mb8" />
@@ -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']"
>修改</el-button>
<el-button
v-if="scope.row.canRemove === true"
link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['master:line:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
@@ -119,7 +114,7 @@
</style>
<script setup name="Line">
import { addLine, delLine, getLine, listLine, updateLine } from "@/api/master/line";
import { addLine, getLine, listLine, updateLine } from "@/api/master/line";
import TestBatchAdoptionDialog from '@/components/TestBatchAdoptionDialog'
import useUserStore from '@/store/modules/user'
import useTestBatchStore from '@/store/modules/testBatch'
@@ -132,7 +127,6 @@ const adoptionDialogRef = ref()
const { sys_normal_disable } = proxy.useDict("sys_normal_disable");
const lineList = ref([]);
const selectedIds = ref([]);
const removableIds = ref([]);
const loading = ref(false);
const detailLoading = ref(false);
const submitLoading = ref(false);
@@ -140,7 +134,6 @@ const loadError = ref("");
const formError = ref("");
const open = ref(false);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref("");
let listGeneration = 0;
@@ -232,11 +225,7 @@ function handleQuery() { queryParams.value.pageNum = 1; getList(); }
function resetQuery() { proxy.resetForm("queryRef"); handleQuery(); }
function handleSelectionChange(selection) {
selectedIds.value = selection.map((item) => item.lineId);
removableIds.value = selection
.filter((item) => item.canRemove === true)
.map((item) => item.lineId);
single.value = selectedIds.value.length !== 1;
multiple.value = removableIds.value.length === 0;
}
function openAdoptionDialog() { adoptionDialogRef.value?.open() }
@@ -319,15 +308,5 @@ function submitUpdate() {
}).finally(() => { submitLoading.value = false; });
});
}
function handleDelete(row) {
const targetIds = row?.lineId ? (row.canRemove === true ? [row.lineId] : []) : removableIds.value;
if (!targetIds.length) { proxy.$modal.msgWarning("请选择允许删除的生产线"); return; }
proxy.$modal.confirm(`是否确认删除产线编号为“${targetIds.join(",")}”的数据项?`).then(() =>
delLine(targetIds.join(","))
).then(() => { proxy.$modal.msgSuccess("删除成功"); getList(); }).catch((error) => {
if (error !== "cancel" && error !== "close") loadError.value = "生产线删除失败,请稍后重试";
});
}
getList();
</script>
@@ -24,10 +24,9 @@
@click="handleUpdate()" v-hasPermi="['master:pallet:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5"><el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['master:pallet:remove']">删除</el-button></el-col>
</el-row>
<el-row v-if="userStore.admin === true && !testBatchStore.isTestMode" :gutter="10" class="mb8 master-toolbar">
<el-col :span="1.5"><el-button type="warning" plain icon="FolderAdd" :disabled="!selectedIds.length" @click="openAdoptionDialog">纳入测试批次</el-button></el-col>
<el-col :span="1.5"><el-button type="warning" plain icon="FolderAdd" :disabled="!selectedIds.length" @click="openAdoptionDialog" v-hasPermi="['system:testbatch:adopt']">纳入测试批次</el-button></el-col>
</el-row>
<el-alert v-if="loadError" :title="loadError" type="error" :closable="false" show-icon class="mb8" />
@@ -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']"
>修改</el-button>
<el-button
v-if="scope.row.canRemove === true"
link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['master:pallet:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
@@ -119,7 +114,7 @@
</style>
<script setup name="Pallet">
import { addPallet, delPallet, getPallet, getPalletQrcode, listPallet, updatePallet } from "@/api/master/pallet";
import { addPallet, getPallet, getPalletQrcode, listPallet, updatePallet } from "@/api/master/pallet";
import TestBatchAdoptionDialog from '@/components/TestBatchAdoptionDialog'
import useUserStore from '@/store/modules/user'
import useTestBatchStore from '@/store/modules/testBatch'
@@ -132,7 +127,6 @@ const adoptionDialogRef = ref()
const { sys_normal_disable } = proxy.useDict("sys_normal_disable");
const palletList = ref([]);
const selectedIds = ref([]);
const removableIds = ref([]);
const loading = ref(false);
const detailLoading = ref(false);
const submitLoading = ref(false);
@@ -140,7 +134,6 @@ const loadError = ref("");
const formError = ref("");
const open = ref(false);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref("");
const printingIds = ref([]);
@@ -232,11 +225,7 @@ function handleQuery() { queryParams.value.pageNum = 1; getList(); }
function resetQuery() { proxy.resetForm("queryRef"); handleQuery(); }
function handleSelectionChange(selection) {
selectedIds.value = selection.map((item) => item.palletId);
removableIds.value = selection
.filter((item) => item.canRemove === true)
.map((item) => item.palletId);
single.value = selectedIds.value.length !== 1;
multiple.value = removableIds.value.length === 0;
}
function openAdoptionDialog() { adoptionDialogRef.value?.open() }
@@ -315,16 +304,6 @@ function submitUpdate() {
}).finally(() => { submitLoading.value = false; });
});
}
function handleDelete(row) {
const targetIds = row?.palletId ? (row.canRemove === true ? [row.palletId] : []) : removableIds.value;
if (!targetIds.length) { proxy.$modal.msgWarning("请选择允许删除的托盘"); return; }
proxy.$modal.confirm(`是否确认删除托盘编号为“${targetIds.join(",")}”的数据项?`).then(() =>
delPallet(targetIds.join(","))
).then(() => { proxy.$modal.msgSuccess("删除成功"); getList(); }).catch((error) => {
if (error !== "cancel" && error !== "close") loadError.value = "托盘删除失败,请稍后重试";
});
}
function isPrinting(palletId) {
return printingIds.value.includes(palletId);
}
@@ -30,11 +30,10 @@
@click="handleUpdate()" v-hasPermi="['master:site:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5"><el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['master:site:remove']">删除</el-button></el-col>
<el-col :span="1.5"><el-button type="warning" plain icon="User" :disabled="single" @click="handleAssign()" v-hasPermi="['master:site:assign']">分配用户</el-button></el-col>
</el-row>
<el-row v-if="userStore.admin === true && !testBatchStore.isTestMode" :gutter="10" class="mb8 master-toolbar">
<el-col :span="1.5"><el-button type="warning" plain icon="FolderAdd" :disabled="!selectedIds.length" @click="openAdoptionDialog">纳入测试批次</el-button></el-col>
<el-col :span="1.5"><el-button type="warning" plain icon="FolderAdd" :disabled="!selectedIds.length" @click="openAdoptionDialog" v-hasPermi="['system:testbatch:adopt']">纳入测试批次</el-button></el-col>
</el-row>
<el-alert v-if="loadError" :title="loadError" type="error" :closable="false" show-icon class="mb8" />
@@ -58,10 +57,6 @@
v-if="proxy.$auth.hasPermiAnd(['master:site:query', 'master:site:edit'])"
link type="primary" icon="Edit" :disabled="detailLoading" @click="handleUpdate(scope.row)" v-hasPermi="['master:site:edit']"
>修改</el-button>
<el-button
v-if="scope.row.canRemove === true"
link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['master:site:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
@@ -128,7 +123,7 @@
<script setup name="Site">
import BusinessUserAssignDialog from "@/components/BusinessUserAssignDialog";
import { addSite, delSite, getSite, listSite, updateSite } from "@/api/master/site";
import { addSite, getSite, listSite, updateSite } from "@/api/master/site";
import TestBatchAdoptionDialog from '@/components/TestBatchAdoptionDialog'
import useUserStore from '@/store/modules/user'
import useTestBatchStore from '@/store/modules/testBatch'
@@ -141,7 +136,6 @@ const adoptionDialogRef = ref()
const { sys_normal_disable } = proxy.useDict("sys_normal_disable");
const siteList = ref([]);
const selectedIds = ref([]);
const removableIds = ref([]);
const selectedRows = ref([]);
const assignDialogRef = ref();
const loading = ref(false);
@@ -151,7 +145,6 @@ const loadError = ref("");
const formError = ref("");
const open = ref(false);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref("");
let listGeneration = 0;
@@ -244,11 +237,7 @@ function resetQuery() { proxy.resetForm("queryRef"); handleQuery(); }
function handleSelectionChange(selection) {
selectedRows.value = selection;
selectedIds.value = selection.map((item) => item.siteId);
removableIds.value = selection
.filter((item) => item.canRemove === true)
.map((item) => item.siteId);
single.value = selectedIds.value.length !== 1;
multiple.value = removableIds.value.length === 0;
}
function openAdoptionDialog() { adoptionDialogRef.value?.open() }
@@ -343,15 +332,5 @@ function submitUpdate() {
}).finally(() => { submitLoading.value = false; });
});
}
function handleDelete(row) {
const targetIds = row?.siteId ? (row.canRemove === true ? [row.siteId] : []) : removableIds.value;
if (!targetIds.length) { proxy.$modal.msgWarning("请选择允许删除的客户现场"); return; }
proxy.$modal.confirm(`是否确认删除现场编号为“${targetIds.join(",")}”的数据项?`).then(() =>
delSite(targetIds.join(","))
).then(() => { proxy.$modal.msgSuccess("删除成功"); getList(); }).catch((error) => {
if (error !== "cancel" && error !== "close") loadError.value = "客户现场删除失败,请稍后重试";
});
}
getList();
</script>
@@ -34,12 +34,9 @@
v-hasPermi="['master:strain:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['master:strain:remove']">删除</el-button>
</el-col>
</el-row>
<el-row v-if="userStore.admin === true && !testBatchStore.isTestMode" :gutter="10" class="mb8 master-toolbar">
<el-col :span="1.5"><el-button type="warning" plain icon="FolderAdd" :disabled="!selectedIds.length" @click="openAdoptionDialog">纳入测试批次</el-button></el-col>
<el-col :span="1.5"><el-button type="warning" plain icon="FolderAdd" :disabled="!selectedIds.length" @click="openAdoptionDialog" v-hasPermi="['system:testbatch:adopt']">纳入测试批次</el-button></el-col>
</el-row>
<el-alert v-if="loadError" :title="loadError" type="error" :closable="false" show-icon class="mb8" />
@@ -68,14 +65,6 @@
@click="handleUpdate(scope.row)"
v-hasPermi="['master:strain:edit']"
>修改</el-button>
<el-button
v-if="scope.row.canRemove === true"
link
type="primary"
icon="Delete"
@click="handleDelete(scope.row)"
v-hasPermi="['master:strain:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
@@ -136,7 +125,7 @@
</style>
<script setup name="Strain">
import { addStrain, delStrain, getStrain, listStrain, updateStrain } from "@/api/master/strain";
import { addStrain, getStrain, listStrain, updateStrain } from "@/api/master/strain";
import TestBatchAdoptionDialog from '@/components/TestBatchAdoptionDialog'
import useUserStore from '@/store/modules/user'
import useTestBatchStore from '@/store/modules/testBatch'
@@ -150,7 +139,6 @@ const { sys_normal_disable } = proxy.useDict("sys_normal_disable");
const strainList = ref([]);
const selectedIds = ref([]);
const removableIds = ref([]);
const loading = ref(false);
const detailLoading = ref(false);
const submitLoading = ref(false);
@@ -158,7 +146,6 @@ const loadError = ref("");
const formError = ref("");
const open = ref(false);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref("");
let listGeneration = 0;
@@ -256,11 +243,7 @@ function resetQuery() {
function handleSelectionChange(selection) {
selectedIds.value = selection.map((item) => item.strainId);
removableIds.value = selection
.filter((item) => item.canRemove === true)
.map((item) => item.strainId);
single.value = selectedIds.value.length !== 1;
multiple.value = removableIds.value.length === 0;
}
function openAdoptionDialog() { adoptionDialogRef.value?.open() }
@@ -350,21 +333,5 @@ function submitUpdate() {
});
}
function handleDelete(row) {
const targetIds = row?.strainId ? (row.canRemove === true ? [row.strainId] : []) : removableIds.value;
if (!targetIds.length) {
proxy.$modal.msgWarning("请选择允许删除的菌种");
return;
}
proxy.$modal.confirm(`是否确认删除菌种编号为“${targetIds.join(",")}”的数据项?`).then(() =>
delStrain(targetIds.join(","))
).then(() => {
proxy.$modal.msgSuccess("删除成功");
getList();
}).catch((error) => {
if (error !== "cancel" && error !== "close") loadError.value = "菌种删除失败,请稍后重试";
});
}
getList();
</script>
@@ -27,11 +27,10 @@
@click="handleUpdate()" v-hasPermi="['master:warehouse:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5"><el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['master:warehouse:remove']">删除</el-button></el-col>
<el-col :span="1.5"><el-button type="warning" plain icon="User" :disabled="single" @click="handleAssign()" v-hasPermi="['master:warehouse:assign']">分配用户</el-button></el-col>
</el-row>
<el-row v-if="userStore.admin === true && !testBatchStore.isTestMode" :gutter="10" class="mb8 master-toolbar">
<el-col :span="1.5"><el-button type="warning" plain icon="FolderAdd" :disabled="!selectedIds.length" @click="openAdoptionDialog">纳入测试批次</el-button></el-col>
<el-col :span="1.5"><el-button type="warning" plain icon="FolderAdd" :disabled="!selectedIds.length" @click="openAdoptionDialog" v-hasPermi="['system:testbatch:adopt']">纳入测试批次</el-button></el-col>
</el-row>
<el-alert v-if="loadError" :title="loadError" type="error" :closable="false" show-icon class="mb8" />
@@ -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']"
>修改</el-button>
<el-button
v-if="scope.row.canRemove === true"
link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['master:warehouse:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
@@ -121,7 +116,7 @@
<script setup name="Warehouse">
import BusinessUserAssignDialog from "@/components/BusinessUserAssignDialog";
import { addWarehouse, delWarehouse, getWarehouse, listWarehouse, updateWarehouse } from "@/api/master/warehouse";
import { addWarehouse, getWarehouse, listWarehouse, updateWarehouse } from "@/api/master/warehouse";
import TestBatchAdoptionDialog from '@/components/TestBatchAdoptionDialog'
import useUserStore from '@/store/modules/user'
import useTestBatchStore from '@/store/modules/testBatch'
@@ -134,7 +129,6 @@ const adoptionDialogRef = ref()
const { sys_normal_disable } = proxy.useDict("sys_normal_disable");
const warehouseList = ref([]);
const selectedIds = ref([]);
const removableIds = ref([]);
const selectedRows = ref([]);
const assignDialogRef = ref();
const loading = ref(false);
@@ -144,7 +138,6 @@ const loadError = ref("");
const formError = ref("");
const open = ref(false);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref("");
let listGeneration = 0;
@@ -229,11 +222,7 @@ function resetQuery() { proxy.resetForm("queryRef"); handleQuery(); }
function handleSelectionChange(selection) {
selectedRows.value = selection;
selectedIds.value = selection.map((item) => item.warehouseId);
removableIds.value = selection
.filter((item) => item.canRemove === true)
.map((item) => item.warehouseId);
single.value = selectedIds.value.length !== 1;
multiple.value = removableIds.value.length === 0;
}
function openAdoptionDialog() { adoptionDialogRef.value?.open() }
@@ -325,15 +314,5 @@ function submitUpdate() {
}).finally(() => { submitLoading.value = false; });
});
}
function handleDelete(row) {
const targetIds = row?.warehouseId ? (row.canRemove === true ? [row.warehouseId] : []) : removableIds.value;
if (!targetIds.length) { proxy.$modal.msgWarning("请选择允许删除的仓库"); return; }
proxy.$modal.confirm(`是否确认删除仓库编号为“${targetIds.join(",")}”的数据项?`).then(() =>
delWarehouse(targetIds.join(","))
).then(() => { proxy.$modal.msgSuccess("删除成功"); getList(); }).catch((error) => {
if (error !== "cancel" && error !== "close") loadError.value = "仓库删除失败,请稍后重试";
});
}
getList();
</script>