安全:隔离主数据并封闭单条删除

This commit is contained in:
焦龙言
2026-09-22 18:16:30 +08:00
parent af5428d0e3
commit b6f606d9f2
13 changed files with 1382 additions and 1131 deletions
@@ -12,6 +12,8 @@ from common.enums import BusinessType
from common.router import APIRouterPro from common.router import APIRouterPro
from common.vo import DataResponseModel, PageResponseModel, ResponseBaseModel from common.vo import DataResponseModel, PageResponseModel, ResponseBaseModel
from module_admin.entity.vo.user_vo import CurrentUserModel from module_admin.entity.vo.user_vo import CurrentUserModel
from module_business.dependency.test_batch_dependency import TestBatchScopeDependency
from module_business.domain.test_batch_scope import TestBatchScope
from module_business.entity.vo.master_vo import CustomerModel, CustomerPageQueryModel, DeleteCustomerModel from module_business.entity.vo.master_vo import CustomerModel, CustomerPageQueryModel, DeleteCustomerModel
from module_business.service.master_service import MasterService from module_business.service.master_service import MasterService
from utils.response_util import ResponseUtil from utils.response_util import ResponseUtil
@@ -32,8 +34,9 @@ async def get_master_customer_list(
request: Request, request: Request,
customer_page_query: Annotated[CustomerPageQueryModel, Query()], customer_page_query: Annotated[CustomerPageQueryModel, Query()],
query_db: Annotated[AsyncSession, DBSessionDependency()], query_db: Annotated[AsyncSession, DBSessionDependency()],
scope: Annotated[TestBatchScope, TestBatchScopeDependency()],
) -> Response: ) -> Response:
customer_page_query_result = await MasterService.list_customer(query_db, customer_page_query, is_page=True) customer_page_query_result = await MasterService.list_customer(query_db, customer_page_query, scope, is_page=True)
return ResponseUtil.success(model_content=customer_page_query_result) return ResponseUtil.success(model_content=customer_page_query_result)
@@ -49,8 +52,9 @@ async def query_detail_master_customer(
request: Request, request: Request,
customer_id: Annotated[int, Path(gt=0, description='客户ID')], customer_id: Annotated[int, Path(gt=0, description='客户ID')],
query_db: Annotated[AsyncSession, DBSessionDependency()], query_db: Annotated[AsyncSession, DBSessionDependency()],
scope: Annotated[TestBatchScope, TestBatchScopeDependency()],
) -> Response: ) -> Response:
customer_detail_result = await MasterService.detail_customer(query_db, customer_id) customer_detail_result = await MasterService.detail_customer(query_db, customer_id, scope)
return ResponseUtil.success(data=customer_detail_result) return ResponseUtil.success(data=customer_detail_result)
@@ -68,13 +72,14 @@ async def add_master_customer(
add_customer: CustomerModel, add_customer: CustomerModel,
query_db: Annotated[AsyncSession, DBSessionDependency()], query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()], current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
scope: Annotated[TestBatchScope, TestBatchScopeDependency(write=True)],
) -> Response: ) -> Response:
current_time = datetime.now() current_time = datetime.now()
add_customer.create_by = current_user.user.user_name add_customer.create_by = current_user.user.user_name
add_customer.create_time = current_time add_customer.create_time = current_time
add_customer.update_by = current_user.user.user_name add_customer.update_by = current_user.user.user_name
add_customer.update_time = current_time add_customer.update_time = current_time
add_customer_result = await MasterService.add_customer(query_db, add_customer) add_customer_result = await MasterService.add_customer(query_db, add_customer, current_user, scope)
return ResponseUtil.success(msg=add_customer_result.message) return ResponseUtil.success(msg=add_customer_result.message)
@@ -92,10 +97,11 @@ async def edit_master_customer(
edit_customer: CustomerModel, edit_customer: CustomerModel,
query_db: Annotated[AsyncSession, DBSessionDependency()], query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()], current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
scope: Annotated[TestBatchScope, TestBatchScopeDependency(write=True)],
) -> Response: ) -> Response:
edit_customer.update_by = current_user.user.user_name edit_customer.update_by = current_user.user.user_name
edit_customer.update_time = datetime.now() edit_customer.update_time = datetime.now()
edit_customer_result = await MasterService.edit_customer(query_db, edit_customer) edit_customer_result = await MasterService.edit_customer(query_db, edit_customer, current_user, scope)
return ResponseUtil.success(msg=edit_customer_result.message) return ResponseUtil.success(msg=edit_customer_result.message)
@@ -12,6 +12,8 @@ from common.enums import BusinessType
from common.router import APIRouterPro from common.router import APIRouterPro
from common.vo import DataResponseModel, PageResponseModel, ResponseBaseModel from common.vo import DataResponseModel, PageResponseModel, ResponseBaseModel
from module_admin.entity.vo.user_vo import CurrentUserModel from module_admin.entity.vo.user_vo import CurrentUserModel
from module_business.dependency.test_batch_dependency import TestBatchScopeDependency
from module_business.domain.test_batch_scope import TestBatchScope
from module_business.entity.vo.master_vo import DeleteLineModel, LineModel, LinePageQueryModel from module_business.entity.vo.master_vo import DeleteLineModel, LineModel, LinePageQueryModel
from module_business.service.master_service import MasterService from module_business.service.master_service import MasterService
from utils.response_util import ResponseUtil from utils.response_util import ResponseUtil
@@ -30,8 +32,9 @@ async def get_master_line_list(
request: Request, request: Request,
line_page_query: Annotated[LinePageQueryModel, Query()], line_page_query: Annotated[LinePageQueryModel, Query()],
query_db: Annotated[AsyncSession, DBSessionDependency()], query_db: Annotated[AsyncSession, DBSessionDependency()],
scope: Annotated[TestBatchScope, TestBatchScopeDependency()],
) -> Response: ) -> Response:
line_page_query_result = await MasterService.list_line(query_db, line_page_query, is_page=True) line_page_query_result = await MasterService.list_line(query_db, line_page_query, scope, is_page=True)
return ResponseUtil.success(model_content=line_page_query_result) return ResponseUtil.success(model_content=line_page_query_result)
@@ -47,8 +50,9 @@ async def query_detail_master_line(
request: Request, request: Request,
line_id: Annotated[int, Path(gt=0, description='生产线ID')], line_id: Annotated[int, Path(gt=0, description='生产线ID')],
query_db: Annotated[AsyncSession, DBSessionDependency()], query_db: Annotated[AsyncSession, DBSessionDependency()],
scope: Annotated[TestBatchScope, TestBatchScopeDependency()],
) -> Response: ) -> Response:
line_detail_result = await MasterService.detail_line(query_db, line_id) line_detail_result = await MasterService.detail_line(query_db, line_id, scope)
return ResponseUtil.success(data=line_detail_result) return ResponseUtil.success(data=line_detail_result)
@@ -66,13 +70,14 @@ async def add_master_line(
add_line: LineModel, add_line: LineModel,
query_db: Annotated[AsyncSession, DBSessionDependency()], query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()], current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
scope: Annotated[TestBatchScope, TestBatchScopeDependency(write=True)],
) -> Response: ) -> Response:
current_time = datetime.now() current_time = datetime.now()
add_line.create_by = current_user.user.user_name add_line.create_by = current_user.user.user_name
add_line.create_time = current_time add_line.create_time = current_time
add_line.update_by = current_user.user.user_name add_line.update_by = current_user.user.user_name
add_line.update_time = current_time add_line.update_time = current_time
add_line_result = await MasterService.add_line(query_db, add_line) add_line_result = await MasterService.add_line(query_db, add_line, current_user, scope)
return ResponseUtil.success(msg=add_line_result.message) return ResponseUtil.success(msg=add_line_result.message)
@@ -90,10 +95,11 @@ async def edit_master_line(
edit_line: LineModel, edit_line: LineModel,
query_db: Annotated[AsyncSession, DBSessionDependency()], query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()], current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
scope: Annotated[TestBatchScope, TestBatchScopeDependency(write=True)],
) -> Response: ) -> Response:
edit_line.update_by = current_user.user.user_name edit_line.update_by = current_user.user.user_name
edit_line.update_time = datetime.now() edit_line.update_time = datetime.now()
edit_line_result = await MasterService.edit_line(query_db, edit_line) edit_line_result = await MasterService.edit_line(query_db, edit_line, current_user, scope)
return ResponseUtil.success(msg=edit_line_result.message) return ResponseUtil.success(msg=edit_line_result.message)
@@ -11,6 +11,8 @@ from common.enums import BusinessType
from common.router import APIRouterPro from common.router import APIRouterPro
from common.vo import DataResponseModel, PageResponseModel, ResponseBaseModel from common.vo import DataResponseModel, PageResponseModel, ResponseBaseModel
from module_admin.entity.vo.user_vo import CurrentUserModel from module_admin.entity.vo.user_vo import CurrentUserModel
from module_business.dependency.test_batch_dependency import TestBatchScopeDependency
from module_business.domain.test_batch_scope import TestBatchScope
from module_business.entity.vo.master_vo import ( from module_business.entity.vo.master_vo import (
DeletePalletModel, DeletePalletModel,
PalletCreateModel, PalletCreateModel,
@@ -37,8 +39,9 @@ async def get_master_pallet_list(
request: Request, request: Request,
pallet_page_query: Annotated[PalletPageQueryModel, Query()], pallet_page_query: Annotated[PalletPageQueryModel, Query()],
query_db: Annotated[AsyncSession, DBSessionDependency()], query_db: Annotated[AsyncSession, DBSessionDependency()],
scope: Annotated[TestBatchScope, TestBatchScopeDependency()],
) -> Response: ) -> Response:
result = await PalletService.list_pallet(query_db, pallet_page_query, is_page=True) result = await PalletService.list_pallet(query_db, pallet_page_query, scope, is_page=True)
return ResponseUtil.success(model_content=result) return ResponseUtil.success(model_content=result)
@@ -54,8 +57,9 @@ async def query_master_pallet_qrcode(
request: Request, request: Request,
pallet_id: Annotated[int, Path(gt=0, description='托盘ID')], pallet_id: Annotated[int, Path(gt=0, description='托盘ID')],
query_db: Annotated[AsyncSession, DBSessionDependency()], query_db: Annotated[AsyncSession, DBSessionDependency()],
scope: Annotated[TestBatchScope, TestBatchScopeDependency()],
) -> Response: ) -> Response:
png = await PalletService.get_pallet_qrcode(query_db, pallet_id) png = await PalletService.get_pallet_qrcode(query_db, pallet_id, scope)
return Response(content=png, media_type='image/png') return Response(content=png, media_type='image/png')
@@ -70,8 +74,9 @@ async def query_detail_master_pallet(
request: Request, request: Request,
pallet_id: Annotated[int, Path(gt=0, description='托盘ID')], pallet_id: Annotated[int, Path(gt=0, description='托盘ID')],
query_db: Annotated[AsyncSession, DBSessionDependency()], query_db: Annotated[AsyncSession, DBSessionDependency()],
scope: Annotated[TestBatchScope, TestBatchScopeDependency()],
) -> Response: ) -> Response:
result = await PalletService.detail_pallet(query_db, pallet_id) result = await PalletService.detail_pallet(query_db, pallet_id, scope)
return ResponseUtil.success(data=result) return ResponseUtil.success(data=result)
@@ -88,8 +93,9 @@ async def add_master_pallet(
add_pallet: PalletCreateModel, add_pallet: PalletCreateModel,
query_db: Annotated[AsyncSession, DBSessionDependency()], query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()], current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
scope: Annotated[TestBatchScope, TestBatchScopeDependency(write=True)],
) -> Response: ) -> Response:
result = await PalletService.add_pallet(query_db, add_pallet, current_user.user.user_name) result = await PalletService.add_pallet(query_db, add_pallet, current_user, scope)
return ResponseUtil.success(msg=result.message) return ResponseUtil.success(msg=result.message)
@@ -106,8 +112,9 @@ async def edit_master_pallet(
edit_pallet: PalletEditModel, edit_pallet: PalletEditModel,
query_db: Annotated[AsyncSession, DBSessionDependency()], query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()], current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
scope: Annotated[TestBatchScope, TestBatchScopeDependency(write=True)],
) -> Response: ) -> Response:
result = await PalletService.edit_pallet(query_db, edit_pallet, current_user.user.user_name) result = await PalletService.edit_pallet(query_db, edit_pallet, current_user, scope)
return ResponseUtil.success(msg=result.message) return ResponseUtil.success(msg=result.message)
@@ -12,6 +12,8 @@ from common.enums import BusinessType
from common.router import APIRouterPro from common.router import APIRouterPro
from common.vo import DataResponseModel, PageResponseModel, ResponseBaseModel from common.vo import DataResponseModel, PageResponseModel, ResponseBaseModel
from module_admin.entity.vo.user_vo import CurrentUserModel from module_admin.entity.vo.user_vo import CurrentUserModel
from module_business.dependency.test_batch_dependency import TestBatchScopeDependency
from module_business.domain.test_batch_scope import TestBatchScope
from module_business.entity.vo.master_vo import ( from module_business.entity.vo.master_vo import (
BusinessUserOptionModel, BusinessUserOptionModel,
DeleteSiteModel, DeleteSiteModel,
@@ -45,8 +47,9 @@ async def get_master_site_users(
request: Request, request: Request,
site_id: Annotated[int, Path(gt=0, description='客户现场ID')], site_id: Annotated[int, Path(gt=0, description='客户现场ID')],
query_db: Annotated[AsyncSession, DBSessionDependency()], query_db: Annotated[AsyncSession, DBSessionDependency()],
scope: Annotated[TestBatchScope, TestBatchScopeDependency()],
) -> Response: ) -> Response:
users = await PalletService.list_site_users(query_db, site_id) users = await PalletService.list_site_users(query_db, site_id, scope)
return ResponseUtil.success(data=users) return ResponseUtil.success(data=users)
@@ -64,8 +67,9 @@ async def assign_master_site_users(
assignment: UserAssignModel, assignment: UserAssignModel,
query_db: Annotated[AsyncSession, DBSessionDependency()], query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, ASSIGN_CURRENT_USER()], current_user: Annotated[CurrentUserModel, ASSIGN_CURRENT_USER()],
scope: Annotated[TestBatchScope, TestBatchScopeDependency(write=True)],
) -> Response: ) -> Response:
result = await PalletService.assign_site_users(query_db, site_id, assignment) result = await PalletService.assign_site_users(query_db, site_id, assignment, current_user, scope)
return ResponseUtil.success(msg=result.message) return ResponseUtil.success(msg=result.message)
@@ -80,8 +84,9 @@ async def get_master_site_list(
request: Request, request: Request,
site_page_query: Annotated[SitePageQueryModel, Query()], site_page_query: Annotated[SitePageQueryModel, Query()],
query_db: Annotated[AsyncSession, DBSessionDependency()], query_db: Annotated[AsyncSession, DBSessionDependency()],
scope: Annotated[TestBatchScope, TestBatchScopeDependency()],
) -> Response: ) -> Response:
site_page_query_result = await MasterService.list_site(query_db, site_page_query, is_page=True) site_page_query_result = await MasterService.list_site(query_db, site_page_query, scope, is_page=True)
return ResponseUtil.success(model_content=site_page_query_result) return ResponseUtil.success(model_content=site_page_query_result)
@@ -97,8 +102,9 @@ async def query_detail_master_site(
request: Request, request: Request,
site_id: Annotated[int, Path(gt=0, description='客户现场ID')], site_id: Annotated[int, Path(gt=0, description='客户现场ID')],
query_db: Annotated[AsyncSession, DBSessionDependency()], query_db: Annotated[AsyncSession, DBSessionDependency()],
scope: Annotated[TestBatchScope, TestBatchScopeDependency()],
) -> Response: ) -> Response:
site_detail_result = await MasterService.detail_site(query_db, site_id) site_detail_result = await MasterService.detail_site(query_db, site_id, scope)
return ResponseUtil.success(data=site_detail_result) return ResponseUtil.success(data=site_detail_result)
@@ -116,13 +122,14 @@ async def add_master_site(
add_site: SiteModel, add_site: SiteModel,
query_db: Annotated[AsyncSession, DBSessionDependency()], query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()], current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
scope: Annotated[TestBatchScope, TestBatchScopeDependency(write=True)],
) -> Response: ) -> Response:
current_time = datetime.now() current_time = datetime.now()
add_site.create_by = current_user.user.user_name add_site.create_by = current_user.user.user_name
add_site.create_time = current_time add_site.create_time = current_time
add_site.update_by = current_user.user.user_name add_site.update_by = current_user.user.user_name
add_site.update_time = current_time add_site.update_time = current_time
add_site_result = await MasterService.add_site(query_db, add_site) add_site_result = await MasterService.add_site(query_db, add_site, current_user, scope)
return ResponseUtil.success(msg=add_site_result.message) return ResponseUtil.success(msg=add_site_result.message)
@@ -140,10 +147,11 @@ async def edit_master_site(
edit_site: SiteModel, edit_site: SiteModel,
query_db: Annotated[AsyncSession, DBSessionDependency()], query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()], current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
scope: Annotated[TestBatchScope, TestBatchScopeDependency(write=True)],
) -> Response: ) -> Response:
edit_site.update_by = current_user.user.user_name edit_site.update_by = current_user.user.user_name
edit_site.update_time = datetime.now() edit_site.update_time = datetime.now()
edit_site_result = await MasterService.edit_site(query_db, edit_site) edit_site_result = await MasterService.edit_site(query_db, edit_site, current_user, scope)
return ResponseUtil.success(msg=edit_site_result.message) return ResponseUtil.success(msg=edit_site_result.message)
@@ -12,6 +12,8 @@ from common.enums import BusinessType
from common.router import APIRouterPro from common.router import APIRouterPro
from common.vo import DataResponseModel, PageResponseModel, ResponseBaseModel from common.vo import DataResponseModel, PageResponseModel, ResponseBaseModel
from module_admin.entity.vo.user_vo import CurrentUserModel from module_admin.entity.vo.user_vo import CurrentUserModel
from module_business.dependency.test_batch_dependency import TestBatchScopeDependency
from module_business.domain.test_batch_scope import TestBatchScope
from module_business.entity.vo.master_vo import DeleteStrainModel, StrainModel, StrainPageQueryModel from module_business.entity.vo.master_vo import DeleteStrainModel, StrainModel, StrainPageQueryModel
from module_business.service.master_service import MasterService from module_business.service.master_service import MasterService
from utils.response_util import ResponseUtil from utils.response_util import ResponseUtil
@@ -32,8 +34,9 @@ async def get_master_strain_list(
request: Request, request: Request,
strain_page_query: Annotated[StrainPageQueryModel, Query()], strain_page_query: Annotated[StrainPageQueryModel, Query()],
query_db: Annotated[AsyncSession, DBSessionDependency()], query_db: Annotated[AsyncSession, DBSessionDependency()],
scope: Annotated[TestBatchScope, TestBatchScopeDependency()],
) -> Response: ) -> Response:
strain_page_query_result = await MasterService.list_strain(query_db, strain_page_query, is_page=True) strain_page_query_result = await MasterService.list_strain(query_db, strain_page_query, scope, is_page=True)
return ResponseUtil.success(model_content=strain_page_query_result) return ResponseUtil.success(model_content=strain_page_query_result)
@@ -49,8 +52,9 @@ async def query_detail_master_strain(
request: Request, request: Request,
strain_id: Annotated[int, Path(gt=0, description='菌种ID')], strain_id: Annotated[int, Path(gt=0, description='菌种ID')],
query_db: Annotated[AsyncSession, DBSessionDependency()], query_db: Annotated[AsyncSession, DBSessionDependency()],
scope: Annotated[TestBatchScope, TestBatchScopeDependency()],
) -> Response: ) -> Response:
strain_detail_result = await MasterService.detail_strain(query_db, strain_id) strain_detail_result = await MasterService.detail_strain(query_db, strain_id, scope)
return ResponseUtil.success(data=strain_detail_result) return ResponseUtil.success(data=strain_detail_result)
@@ -68,13 +72,14 @@ async def add_master_strain(
add_strain: StrainModel, add_strain: StrainModel,
query_db: Annotated[AsyncSession, DBSessionDependency()], query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()], current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
scope: Annotated[TestBatchScope, TestBatchScopeDependency(write=True)],
) -> Response: ) -> Response:
current_time = datetime.now() current_time = datetime.now()
add_strain.create_by = current_user.user.user_name add_strain.create_by = current_user.user.user_name
add_strain.create_time = current_time add_strain.create_time = current_time
add_strain.update_by = current_user.user.user_name add_strain.update_by = current_user.user.user_name
add_strain.update_time = current_time add_strain.update_time = current_time
add_strain_result = await MasterService.add_strain(query_db, add_strain) add_strain_result = await MasterService.add_strain(query_db, add_strain, current_user, scope)
return ResponseUtil.success(msg=add_strain_result.message) return ResponseUtil.success(msg=add_strain_result.message)
@@ -92,10 +97,11 @@ async def edit_master_strain(
edit_strain: StrainModel, edit_strain: StrainModel,
query_db: Annotated[AsyncSession, DBSessionDependency()], query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()], current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
scope: Annotated[TestBatchScope, TestBatchScopeDependency(write=True)],
) -> Response: ) -> Response:
edit_strain.update_by = current_user.user.user_name edit_strain.update_by = current_user.user.user_name
edit_strain.update_time = datetime.now() edit_strain.update_time = datetime.now()
edit_strain_result = await MasterService.edit_strain(query_db, edit_strain) edit_strain_result = await MasterService.edit_strain(query_db, edit_strain, current_user, scope)
return ResponseUtil.success(msg=edit_strain_result.message) return ResponseUtil.success(msg=edit_strain_result.message)
@@ -12,6 +12,8 @@ from common.enums import BusinessType
from common.router import APIRouterPro from common.router import APIRouterPro
from common.vo import DataResponseModel, PageResponseModel, ResponseBaseModel from common.vo import DataResponseModel, PageResponseModel, ResponseBaseModel
from module_admin.entity.vo.user_vo import CurrentUserModel from module_admin.entity.vo.user_vo import CurrentUserModel
from module_business.dependency.test_batch_dependency import TestBatchScopeDependency
from module_business.domain.test_batch_scope import TestBatchScope
from module_business.entity.vo.master_vo import ( from module_business.entity.vo.master_vo import (
BusinessUserOptionModel, BusinessUserOptionModel,
DeleteWarehouseModel, DeleteWarehouseModel,
@@ -45,8 +47,9 @@ async def get_master_warehouse_users(
request: Request, request: Request,
warehouse_id: Annotated[int, Path(gt=0, description='仓库ID')], warehouse_id: Annotated[int, Path(gt=0, description='仓库ID')],
query_db: Annotated[AsyncSession, DBSessionDependency()], query_db: Annotated[AsyncSession, DBSessionDependency()],
scope: Annotated[TestBatchScope, TestBatchScopeDependency()],
) -> Response: ) -> Response:
users = await PalletService.list_warehouse_users(query_db, warehouse_id) users = await PalletService.list_warehouse_users(query_db, warehouse_id, scope)
return ResponseUtil.success(data=users) return ResponseUtil.success(data=users)
@@ -64,8 +67,9 @@ async def assign_master_warehouse_users(
assignment: UserAssignModel, assignment: UserAssignModel,
query_db: Annotated[AsyncSession, DBSessionDependency()], query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, ASSIGN_CURRENT_USER()], current_user: Annotated[CurrentUserModel, ASSIGN_CURRENT_USER()],
scope: Annotated[TestBatchScope, TestBatchScopeDependency(write=True)],
) -> Response: ) -> Response:
result = await PalletService.assign_warehouse_users(query_db, warehouse_id, assignment) result = await PalletService.assign_warehouse_users(query_db, warehouse_id, assignment, current_user, scope)
return ResponseUtil.success(msg=result.message) return ResponseUtil.success(msg=result.message)
@@ -80,8 +84,14 @@ async def get_master_warehouse_list(
request: Request, request: Request,
warehouse_page_query: Annotated[WarehousePageQueryModel, Query()], warehouse_page_query: Annotated[WarehousePageQueryModel, Query()],
query_db: Annotated[AsyncSession, DBSessionDependency()], query_db: Annotated[AsyncSession, DBSessionDependency()],
scope: Annotated[TestBatchScope, TestBatchScopeDependency()],
) -> Response: ) -> Response:
warehouse_page_query_result = await MasterService.list_warehouse(query_db, warehouse_page_query, is_page=True) warehouse_page_query_result = await MasterService.list_warehouse(
query_db,
warehouse_page_query,
scope,
is_page=True,
)
return ResponseUtil.success(model_content=warehouse_page_query_result) return ResponseUtil.success(model_content=warehouse_page_query_result)
@@ -97,8 +107,9 @@ async def query_detail_master_warehouse(
request: Request, request: Request,
warehouse_id: Annotated[int, Path(gt=0, description='仓库ID')], warehouse_id: Annotated[int, Path(gt=0, description='仓库ID')],
query_db: Annotated[AsyncSession, DBSessionDependency()], query_db: Annotated[AsyncSession, DBSessionDependency()],
scope: Annotated[TestBatchScope, TestBatchScopeDependency()],
) -> Response: ) -> Response:
warehouse_detail_result = await MasterService.detail_warehouse(query_db, warehouse_id) warehouse_detail_result = await MasterService.detail_warehouse(query_db, warehouse_id, scope)
return ResponseUtil.success(data=warehouse_detail_result) return ResponseUtil.success(data=warehouse_detail_result)
@@ -116,13 +127,14 @@ async def add_master_warehouse(
add_warehouse: WarehouseModel, add_warehouse: WarehouseModel,
query_db: Annotated[AsyncSession, DBSessionDependency()], query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()], current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
scope: Annotated[TestBatchScope, TestBatchScopeDependency(write=True)],
) -> Response: ) -> Response:
current_time = datetime.now() current_time = datetime.now()
add_warehouse.create_by = current_user.user.user_name add_warehouse.create_by = current_user.user.user_name
add_warehouse.create_time = current_time add_warehouse.create_time = current_time
add_warehouse.update_by = current_user.user.user_name add_warehouse.update_by = current_user.user.user_name
add_warehouse.update_time = current_time add_warehouse.update_time = current_time
add_warehouse_result = await MasterService.add_warehouse(query_db, add_warehouse) add_warehouse_result = await MasterService.add_warehouse(query_db, add_warehouse, current_user, scope)
return ResponseUtil.success(msg=add_warehouse_result.message) return ResponseUtil.success(msg=add_warehouse_result.message)
@@ -140,10 +152,11 @@ async def edit_master_warehouse(
edit_warehouse: WarehouseModel, edit_warehouse: WarehouseModel,
query_db: Annotated[AsyncSession, DBSessionDependency()], query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()], current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
scope: Annotated[TestBatchScope, TestBatchScopeDependency(write=True)],
) -> Response: ) -> Response:
edit_warehouse.update_by = current_user.user.user_name edit_warehouse.update_by = current_user.user.user_name
edit_warehouse.update_time = datetime.now() edit_warehouse.update_time = datetime.now()
edit_warehouse_result = await MasterService.edit_warehouse(query_db, edit_warehouse) edit_warehouse_result = await MasterService.edit_warehouse(query_db, edit_warehouse, current_user, scope)
return ResponseUtil.success(msg=edit_warehouse_result.message) return ResponseUtil.success(msg=edit_warehouse_result.message)
@@ -1,14 +1,14 @@
from datetime import datetime from datetime import datetime
from typing import Any, Literal from typing import Any
from pydantic import BaseModel from pydantic import BaseModel
from sqlalchemy import ColumnElement, Select, and_, delete, func, or_, select, update from sqlalchemy import ColumnElement, Select, select, update
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm.attributes import InstrumentedAttribute from sqlalchemy.orm.attributes import InstrumentedAttribute
from common.vo import PageModel from common.vo import PageModel
from module_admin.entity.do.user_do import SysUser from module_admin.entity.do.user_do import SysUser
from module_business.domain.enums import PalletFlowState from module_business.domain.test_batch_scope import TestBatchScope, batch_scope_condition
from module_business.entity.do.master_do import ( from module_business.entity.do.master_do import (
BizCustomer, BizCustomer,
BizCustomerSite, BizCustomerSite,
@@ -19,18 +19,7 @@ from module_business.entity.do.master_do import (
BizUserWarehouse, BizUserWarehouse,
BizWarehouse, BizWarehouse,
) )
from module_business.entity.do.operation_do import ( from module_business.entity.do.operation_do import BizPalletInventory
BizFillingRecord,
BizInboundItem,
BizInboundOrder,
BizPalletEvent,
BizPalletInventory,
BizReturnItem,
BizReturnOrder,
BizShipmentItem,
BizShipmentOrder,
BizUsageRecord,
)
from module_business.entity.vo.master_vo import ( from module_business.entity.vo.master_vo import (
CustomerModel, CustomerModel,
CustomerPageQueryModel, CustomerPageQueryModel,
@@ -47,54 +36,6 @@ from module_business.entity.vo.master_vo import (
) )
from utils.page_util import PageUtil from utils.page_util import PageUtil
MasterResource = Literal['strain', 'customer', 'site', 'line', 'warehouse']
_REFERENCE_COLUMNS: dict[MasterResource, tuple[tuple[InstrumentedAttribute, ...], ...]] = {
'strain': ((BizFillingRecord.strain_id,),),
'customer': ((BizCustomerSite.customer_id,),),
'site': (
(BizProductionLine.site_id,),
(BizUserSite.site_id,),
(BizShipmentOrder.target_site_id,),
(BizUsageRecord.site_id,),
(BizReturnOrder.source_site_id,),
(BizPalletInventory.site_id,),
(BizPalletEvent.before_site_id, BizPalletEvent.after_site_id),
),
'line': (
(BizUsageRecord.line_id,),
(BizPalletInventory.current_line_id,),
(BizPalletEvent.before_line_id, BizPalletEvent.after_line_id),
),
'warehouse': (
(BizUserWarehouse.warehouse_id,),
(BizInboundOrder.warehouse_id,),
(BizFillingRecord.warehouse_id,),
(BizShipmentOrder.source_warehouse_id,),
(BizReturnOrder.target_warehouse_id,),
(BizPalletInventory.warehouse_id,),
(BizPalletEvent.before_warehouse_id, BizPalletEvent.after_warehouse_id),
),
}
_MASTER_ID_COLUMNS: dict[MasterResource, InstrumentedAttribute] = {
'strain': BizStrain.strain_id,
'customer': BizCustomer.customer_id,
'site': BizCustomerSite.site_id,
'line': BizProductionLine.line_id,
'warehouse': BizWarehouse.warehouse_id,
}
_PALLET_REMOVAL_BLOCKING_COLUMNS: tuple[InstrumentedAttribute, ...] = (
BizInboundItem.pallet_id,
BizFillingRecord.pallet_id,
BizShipmentItem.pallet_id,
BizUsageRecord.pallet_id,
BizReturnItem.pallet_id,
BizPalletEvent.pallet_id,
)
class MasterDao: class MasterDao:
"""菌种、客户、现场、产线和仓库主数据数据库操作层。""" """菌种、客户、现场、产线和仓库主数据数据库操作层。"""
@@ -120,10 +61,11 @@ class MasterDao:
model: type[Any], model: type[Any],
id_column: InstrumentedAttribute, id_column: InstrumentedAttribute,
resource_id: int, resource_id: int,
scope_predicate: ColumnElement[bool],
*, *,
for_update: bool = False, for_update: bool = False,
) -> Any | None: ) -> Any | None:
statement = select(model).where(id_column == resource_id) statement = select(model).where(id_column == resource_id, scope_predicate)
if for_update: if for_update:
statement = statement.with_for_update().execution_options(populate_existing=True) statement = statement.with_for_update().execution_options(populate_existing=True)
result = await db.execute(statement) result = await db.execute(statement)
@@ -146,8 +88,10 @@ class MasterDao:
model: type[Any], model: type[Any],
id_field: str, id_field: str,
page_object: BaseModel, page_object: BaseModel,
test_batch_id: int | None,
) -> Any: ) -> Any:
payload = page_object.model_dump(exclude={id_field, 'can_remove'}) payload = page_object.model_dump(exclude={id_field, 'can_remove', 'test_batch_id'})
payload['test_batch_id'] = test_batch_id
record = model(**payload) record = model(**payload)
db.add(record) db.add(record)
await db.flush() await db.flush()
@@ -160,63 +104,31 @@ class MasterDao:
id_column: InstrumentedAttribute, id_column: InstrumentedAttribute,
id_field: str, id_field: str,
page_object: BaseModel, page_object: BaseModel,
scope: TestBatchScope,
) -> None: ) -> None:
payload = page_object.model_dump( payload = page_object.model_dump(
exclude_unset=True, exclude_unset=True,
exclude={'can_remove', 'create_by', 'create_time'}, exclude={'can_remove', 'create_by', 'create_time', 'test_batch_id'},
) )
resource_id = payload.pop(id_field) resource_id = payload.pop(id_field)
await db.execute(update(model).where(id_column == resource_id).values(**payload)) await db.execute(
update(model)
@staticmethod .where(
async def _delete( id_column == resource_id,
db: AsyncSession, batch_scope_condition(model.test_batch_id, scope),
model: type[Any], )
id_column: InstrumentedAttribute, .values(**payload)
resource_id: int, )
) -> None:
await db.execute(delete(model).where(id_column == resource_id))
@classmethod
async def get_master_removability(
cls,
db: AsyncSession,
resource_type: MasterResource,
resource_ids: list[int],
) -> dict[int, bool]:
"""按删除约束批量判断五类主数据是否允许删除。"""
removability = dict.fromkeys(resource_ids, False)
if not resource_ids:
return removability
id_column = _MASTER_ID_COLUMNS.get(resource_type)
references = _REFERENCE_COLUMNS.get(resource_type)
if id_column is None or references is None:
raise ValueError(f'不支持的主数据类型:{resource_type}')
has_no_references = []
for columns in references:
predicates = [column == id_column for column in columns]
predicate = predicates[0] if len(predicates) == 1 else or_(*predicates)
has_no_references.append(~select(columns[0]).where(predicate).exists())
statement = select(
id_column,
and_(*has_no_references).label('can_remove'),
).where(id_column.in_(resource_ids))
result = await db.execute(statement)
for resource_id, can_remove in result.all():
removability[resource_id] = bool(can_remove)
return removability
@classmethod @classmethod
async def list_strain( async def list_strain(
cls, cls,
db: AsyncSession, db: AsyncSession,
query_object: StrainPageQueryModel, query_object: StrainPageQueryModel,
scope: TestBatchScope,
is_page: bool = True, is_page: bool = True,
) -> PageModel | list[dict[str, Any]]: ) -> PageModel | list[dict[str, Any]]:
conditions = [] conditions = [batch_scope_condition(BizStrain.test_batch_id, scope)]
if query_object.strain_code: if query_object.strain_code:
conditions.append(BizStrain.strain_code.like(f'%{query_object.strain_code}%')) conditions.append(BizStrain.strain_code.like(f'%{query_object.strain_code}%'))
if query_object.strain_name: if query_object.strain_name:
@@ -231,6 +143,7 @@ class MasterDao:
cls, cls,
db: AsyncSession, db: AsyncSession,
strain_id: int, strain_id: int,
scope: TestBatchScope,
*, *,
for_update: bool = False, for_update: bool = False,
) -> BizStrain | None: ) -> BizStrain | None:
@@ -239,6 +152,7 @@ class MasterDao:
BizStrain, BizStrain,
BizStrain.strain_id, BizStrain.strain_id,
strain_id, strain_id,
batch_scope_condition(BizStrain.test_batch_id, scope),
for_update=for_update, for_update=for_update,
) )
@@ -247,25 +161,22 @@ class MasterDao:
return await cls._find(db, BizStrain, BizStrain.strain_code, strain_code) return await cls._find(db, BizStrain, BizStrain.strain_code, strain_code)
@classmethod @classmethod
async def add_strain(cls, db: AsyncSession, strain: StrainModel) -> BizStrain: async def add_strain(cls, db: AsyncSession, strain: StrainModel, test_batch_id: int | None) -> BizStrain:
return await cls._add(db, BizStrain, 'strain_id', strain) return await cls._add(db, BizStrain, 'strain_id', strain, test_batch_id)
@classmethod @classmethod
async def update_strain(cls, db: AsyncSession, strain: StrainModel) -> None: async def update_strain(cls, db: AsyncSession, strain: StrainModel, scope: TestBatchScope) -> None:
await cls._update(db, BizStrain, BizStrain.strain_id, 'strain_id', strain) await cls._update(db, BizStrain, BizStrain.strain_id, 'strain_id', strain, scope)
@classmethod
async def delete_strain(cls, db: AsyncSession, strain_id: int) -> None:
await cls._delete(db, BizStrain, BizStrain.strain_id, strain_id)
@classmethod @classmethod
async def list_customer( async def list_customer(
cls, cls,
db: AsyncSession, db: AsyncSession,
query_object: CustomerPageQueryModel, query_object: CustomerPageQueryModel,
scope: TestBatchScope,
is_page: bool = True, is_page: bool = True,
) -> PageModel | list[dict[str, Any]]: ) -> PageModel | list[dict[str, Any]]:
conditions = [] conditions = [batch_scope_condition(BizCustomer.test_batch_id, scope)]
if query_object.customer_code: if query_object.customer_code:
conditions.append(BizCustomer.customer_code.like(f'%{query_object.customer_code}%')) conditions.append(BizCustomer.customer_code.like(f'%{query_object.customer_code}%'))
if query_object.customer_name: if query_object.customer_name:
@@ -276,33 +187,44 @@ class MasterDao:
return await cls._paginate(db, statement, query_object, is_page) return await cls._paginate(db, statement, query_object, is_page)
@classmethod @classmethod
async def get_customer(cls, db: AsyncSession, customer_id: int) -> BizCustomer | None: async def get_customer(
return await cls._get(db, BizCustomer, BizCustomer.customer_id, customer_id) cls,
db: AsyncSession,
customer_id: int,
scope: TestBatchScope,
*,
for_update: bool = False,
) -> BizCustomer | None:
return await cls._get(
db,
BizCustomer,
BizCustomer.customer_id,
customer_id,
batch_scope_condition(BizCustomer.test_batch_id, scope),
for_update=for_update,
)
@classmethod @classmethod
async def find_customer_by_code(cls, db: AsyncSession, customer_code: str) -> BizCustomer | None: async def find_customer_by_code(cls, db: AsyncSession, customer_code: str) -> BizCustomer | None:
return await cls._find(db, BizCustomer, BizCustomer.customer_code, customer_code) return await cls._find(db, BizCustomer, BizCustomer.customer_code, customer_code)
@classmethod @classmethod
async def add_customer(cls, db: AsyncSession, customer: CustomerModel) -> BizCustomer: async def add_customer(cls, db: AsyncSession, customer: CustomerModel, test_batch_id: int | None) -> BizCustomer:
return await cls._add(db, BizCustomer, 'customer_id', customer) return await cls._add(db, BizCustomer, 'customer_id', customer, test_batch_id)
@classmethod @classmethod
async def update_customer(cls, db: AsyncSession, customer: CustomerModel) -> None: async def update_customer(cls, db: AsyncSession, customer: CustomerModel, scope: TestBatchScope) -> None:
await cls._update(db, BizCustomer, BizCustomer.customer_id, 'customer_id', customer) await cls._update(db, BizCustomer, BizCustomer.customer_id, 'customer_id', customer, scope)
@classmethod
async def delete_customer(cls, db: AsyncSession, customer_id: int) -> None:
await cls._delete(db, BizCustomer, BizCustomer.customer_id, customer_id)
@classmethod @classmethod
async def list_site( async def list_site(
cls, cls,
db: AsyncSession, db: AsyncSession,
query_object: SitePageQueryModel, query_object: SitePageQueryModel,
scope: TestBatchScope,
is_page: bool = True, is_page: bool = True,
) -> PageModel | list[dict[str, Any]]: ) -> PageModel | list[dict[str, Any]]:
conditions = [] conditions = [batch_scope_condition(BizCustomerSite.test_batch_id, scope)]
if query_object.customer_id: if query_object.customer_id:
conditions.append(BizCustomerSite.customer_id == query_object.customer_id) conditions.append(BizCustomerSite.customer_id == query_object.customer_id)
if query_object.site_code: if query_object.site_code:
@@ -319,6 +241,7 @@ class MasterDao:
cls, cls,
db: AsyncSession, db: AsyncSession,
site_id: int, site_id: int,
scope: TestBatchScope,
*, *,
for_update: bool = False, for_update: bool = False,
) -> BizCustomerSite | None: ) -> BizCustomerSite | None:
@@ -327,6 +250,7 @@ class MasterDao:
BizCustomerSite, BizCustomerSite,
BizCustomerSite.site_id, BizCustomerSite.site_id,
site_id, site_id,
batch_scope_condition(BizCustomerSite.test_batch_id, scope),
for_update=for_update, for_update=for_update,
) )
@@ -346,25 +270,22 @@ class MasterDao:
) )
@classmethod @classmethod
async def add_site(cls, db: AsyncSession, site: SiteModel) -> BizCustomerSite: async def add_site(cls, db: AsyncSession, site: SiteModel, test_batch_id: int | None) -> BizCustomerSite:
return await cls._add(db, BizCustomerSite, 'site_id', site) return await cls._add(db, BizCustomerSite, 'site_id', site, test_batch_id)
@classmethod @classmethod
async def update_site(cls, db: AsyncSession, site: SiteModel) -> None: async def update_site(cls, db: AsyncSession, site: SiteModel, scope: TestBatchScope) -> None:
await cls._update(db, BizCustomerSite, BizCustomerSite.site_id, 'site_id', site) await cls._update(db, BizCustomerSite, BizCustomerSite.site_id, 'site_id', site, scope)
@classmethod
async def delete_site(cls, db: AsyncSession, site_id: int) -> None:
await cls._delete(db, BizCustomerSite, BizCustomerSite.site_id, site_id)
@classmethod @classmethod
async def list_line( async def list_line(
cls, cls,
db: AsyncSession, db: AsyncSession,
query_object: LinePageQueryModel, query_object: LinePageQueryModel,
scope: TestBatchScope,
is_page: bool = True, is_page: bool = True,
) -> PageModel | list[dict[str, Any]]: ) -> PageModel | list[dict[str, Any]]:
conditions = [] conditions = [batch_scope_condition(BizProductionLine.test_batch_id, scope)]
if query_object.site_id: if query_object.site_id:
conditions.append(BizProductionLine.site_id == query_object.site_id) conditions.append(BizProductionLine.site_id == query_object.site_id)
if query_object.line_code: if query_object.line_code:
@@ -381,6 +302,7 @@ class MasterDao:
cls, cls,
db: AsyncSession, db: AsyncSession,
line_id: int, line_id: int,
scope: TestBatchScope,
*, *,
for_update: bool = False, for_update: bool = False,
) -> BizProductionLine | None: ) -> BizProductionLine | None:
@@ -389,6 +311,7 @@ class MasterDao:
BizProductionLine, BizProductionLine,
BizProductionLine.line_id, BizProductionLine.line_id,
line_id, line_id,
batch_scope_condition(BizProductionLine.test_batch_id, scope),
for_update=for_update, for_update=for_update,
) )
@@ -408,25 +331,22 @@ class MasterDao:
) )
@classmethod @classmethod
async def add_line(cls, db: AsyncSession, line: LineModel) -> BizProductionLine: async def add_line(cls, db: AsyncSession, line: LineModel, test_batch_id: int | None) -> BizProductionLine:
return await cls._add(db, BizProductionLine, 'line_id', line) return await cls._add(db, BizProductionLine, 'line_id', line, test_batch_id)
@classmethod @classmethod
async def update_line(cls, db: AsyncSession, line: LineModel) -> None: async def update_line(cls, db: AsyncSession, line: LineModel, scope: TestBatchScope) -> None:
await cls._update(db, BizProductionLine, BizProductionLine.line_id, 'line_id', line) await cls._update(db, BizProductionLine, BizProductionLine.line_id, 'line_id', line, scope)
@classmethod
async def delete_line(cls, db: AsyncSession, line_id: int) -> None:
await cls._delete(db, BizProductionLine, BizProductionLine.line_id, line_id)
@classmethod @classmethod
async def list_warehouse( async def list_warehouse(
cls, cls,
db: AsyncSession, db: AsyncSession,
query_object: WarehousePageQueryModel, query_object: WarehousePageQueryModel,
scope: TestBatchScope,
is_page: bool = True, is_page: bool = True,
) -> PageModel | list[dict[str, Any]]: ) -> PageModel | list[dict[str, Any]]:
conditions = [] conditions = [batch_scope_condition(BizWarehouse.test_batch_id, scope)]
if query_object.warehouse_code: if query_object.warehouse_code:
conditions.append(BizWarehouse.warehouse_code.like(f'%{query_object.warehouse_code}%')) conditions.append(BizWarehouse.warehouse_code.like(f'%{query_object.warehouse_code}%'))
if query_object.warehouse_name: if query_object.warehouse_name:
@@ -441,6 +361,7 @@ class MasterDao:
cls, cls,
db: AsyncSession, db: AsyncSession,
warehouse_id: int, warehouse_id: int,
scope: TestBatchScope,
*, *,
for_update: bool = False, for_update: bool = False,
) -> BizWarehouse | None: ) -> BizWarehouse | None:
@@ -449,6 +370,7 @@ class MasterDao:
BizWarehouse, BizWarehouse,
BizWarehouse.warehouse_id, BizWarehouse.warehouse_id,
warehouse_id, warehouse_id,
batch_scope_condition(BizWarehouse.test_batch_id, scope),
for_update=for_update, for_update=for_update,
) )
@@ -457,25 +379,22 @@ class MasterDao:
return await cls._find(db, BizWarehouse, BizWarehouse.warehouse_code, warehouse_code) return await cls._find(db, BizWarehouse, BizWarehouse.warehouse_code, warehouse_code)
@classmethod @classmethod
async def add_warehouse(cls, db: AsyncSession, warehouse: WarehouseModel) -> BizWarehouse: async def add_warehouse(cls, db: AsyncSession, warehouse: WarehouseModel, test_batch_id: int | None) -> BizWarehouse:
return await cls._add(db, BizWarehouse, 'warehouse_id', warehouse) return await cls._add(db, BizWarehouse, 'warehouse_id', warehouse, test_batch_id)
@classmethod @classmethod
async def update_warehouse(cls, db: AsyncSession, warehouse: WarehouseModel) -> None: async def update_warehouse(cls, db: AsyncSession, warehouse: WarehouseModel, scope: TestBatchScope) -> None:
await cls._update(db, BizWarehouse, BizWarehouse.warehouse_id, 'warehouse_id', warehouse) await cls._update(db, BizWarehouse, BizWarehouse.warehouse_id, 'warehouse_id', warehouse, scope)
@classmethod
async def delete_warehouse(cls, db: AsyncSession, warehouse_id: int) -> None:
await cls._delete(db, BizWarehouse, BizWarehouse.warehouse_id, warehouse_id)
@classmethod @classmethod
async def list_pallet( async def list_pallet(
cls, cls,
db: AsyncSession, db: AsyncSession,
query_object: PalletPageQueryModel, query_object: PalletPageQueryModel,
scope: TestBatchScope,
is_page: bool = True, is_page: bool = True,
) -> PageModel | list[dict[str, Any]]: ) -> PageModel | list[dict[str, Any]]:
conditions = [] conditions = [batch_scope_condition(BizPallet.test_batch_id, scope)]
if query_object.pallet_code: if query_object.pallet_code:
conditions.append(BizPallet.pallet_code.like(f'%{query_object.pallet_code}%')) conditions.append(BizPallet.pallet_code.like(f'%{query_object.pallet_code}%'))
if query_object.asset_status: if query_object.asset_status:
@@ -488,45 +407,18 @@ class MasterDao:
cls, cls,
db: AsyncSession, db: AsyncSession,
pallet_id: int, pallet_id: int,
scope: TestBatchScope,
*, *,
for_update: bool = False, for_update: bool = False,
) -> BizPallet | None: ) -> BizPallet | None:
statement = select(BizPallet).where(BizPallet.pallet_id == pallet_id) return await cls._get(
if for_update: db,
statement = statement.with_for_update() BizPallet,
result = await db.execute(statement)
return result.scalar_one_or_none()
@classmethod
async def get_pallet_removability(
cls,
db: AsyncSession,
pallet_ids: list[int],
) -> dict[int, bool]:
"""批量判断托盘是否仍为未入库且没有任何业务引用。"""
removability = dict.fromkeys(pallet_ids, False)
if not pallet_ids:
return removability
has_unstocked_inventory = (
select(BizPalletInventory.pallet_id)
.where(
BizPalletInventory.pallet_id == BizPallet.pallet_id,
BizPalletInventory.flow_state == PalletFlowState.UNSTOCKED.value,
)
.exists()
)
has_no_business_references = tuple(
~select(column).where(column == BizPallet.pallet_id).exists() for column in _PALLET_REMOVAL_BLOCKING_COLUMNS
)
statement = select(
BizPallet.pallet_id, BizPallet.pallet_id,
and_(has_unstocked_inventory, *has_no_business_references).label('can_remove'), pallet_id,
).where(BizPallet.pallet_id.in_(pallet_ids)) batch_scope_condition(BizPallet.test_batch_id, scope),
result = await db.execute(statement) for_update=for_update,
for pallet_id, can_remove in result.all(): )
removability[pallet_id] = bool(can_remove)
return removability
@classmethod @classmethod
async def find_pallet_by_code(cls, db: AsyncSession, pallet_code: str) -> BizPallet | None: async def find_pallet_by_code(cls, db: AsyncSession, pallet_code: str) -> BizPallet | None:
@@ -543,12 +435,16 @@ class MasterDao:
cls, cls,
db: AsyncSession, db: AsyncSession,
pallet: PalletEditModel, pallet: PalletEditModel,
scope: TestBatchScope,
update_by: str, update_by: str,
update_time: datetime, update_time: datetime,
) -> None: ) -> None:
await db.execute( await db.execute(
update(BizPallet) update(BizPallet)
.where(BizPallet.pallet_id == pallet.pallet_id) .where(
BizPallet.pallet_id == pallet.pallet_id,
batch_scope_condition(BizPallet.test_batch_id, scope),
)
.values( .values(
capacity_packages=pallet.capacity_packages, capacity_packages=pallet.capacity_packages,
asset_status=pallet.asset_status, asset_status=pallet.asset_status,
@@ -558,21 +454,21 @@ class MasterDao:
) )
) )
@classmethod
async def delete_pallet(cls, db: AsyncSession, pallet_id: int) -> None:
await cls._delete(db, BizPallet, BizPallet.pallet_id, pallet_id)
@classmethod @classmethod
async def get_pallet_inventory( async def get_pallet_inventory(
cls, cls,
db: AsyncSession, db: AsyncSession,
pallet_id: int, pallet_id: int,
scope: TestBatchScope,
*, *,
for_update: bool = False, for_update: bool = False,
) -> BizPalletInventory | None: ) -> BizPalletInventory | None:
statement = select(BizPalletInventory).where(BizPalletInventory.pallet_id == pallet_id) statement = select(BizPalletInventory).where(
BizPalletInventory.pallet_id == pallet_id,
batch_scope_condition(BizPalletInventory.test_batch_id, scope),
)
if for_update: if for_update:
statement = statement.with_for_update() statement = statement.with_for_update().execution_options(populate_existing=True)
result = await db.execute(statement) result = await db.execute(statement)
return result.scalar_one_or_none() return result.scalar_one_or_none()
@@ -586,17 +482,6 @@ class MasterDao:
await db.flush() await db.flush()
return inventory return inventory
@classmethod
async def delete_pallet_inventory(cls, db: AsyncSession, pallet_id: int) -> None:
await cls._delete(db, BizPalletInventory, BizPalletInventory.pallet_id, pallet_id)
@classmethod
async def count_pallet_events(cls, db: AsyncSession, pallet_id: int) -> int:
result = await db.execute(
select(func.count('*')).select_from(BizPalletEvent).where(BizPalletEvent.pallet_id == pallet_id)
)
return result.scalar() or 0
@classmethod @classmethod
async def list_assignable_users(cls, db: AsyncSession) -> list[Any]: async def list_assignable_users(cls, db: AsyncSession) -> list[Any]:
result = await db.execute( result = await db.execute(
@@ -622,31 +507,35 @@ class MasterDao:
return list(result.all()) return list(result.all())
@classmethod @classmethod
async def list_warehouse_assigned_user_ids(cls, db: AsyncSession, warehouse_id: int) -> list[int]: async def list_warehouse_assigned_user_ids(
result = await db.execute(select(BizUserWarehouse.user_id).where(BizUserWarehouse.warehouse_id == warehouse_id))
return list(result.scalars().all())
@classmethod
async def list_site_assigned_user_ids(cls, db: AsyncSession, site_id: int) -> list[int]:
result = await db.execute(select(BizUserSite.user_id).where(BizUserSite.site_id == site_id))
return list(result.scalars().all())
@classmethod
async def count_references(
cls, cls,
db: AsyncSession, db: AsyncSession,
resource_type: MasterResource, warehouse_id: int,
resource_id: int, scope: TestBatchScope,
) -> int: ) -> list[int]:
"""统计主数据的所有直接外键引用;同表的多个位置字段按一行计数。""" result = await db.execute(
references = _REFERENCE_COLUMNS.get(resource_type) select(BizUserWarehouse.user_id)
if references is None: .join(BizWarehouse, BizWarehouse.warehouse_id == BizUserWarehouse.warehouse_id)
raise ValueError(f'不支持的主数据类型:{resource_type}') .where(
BizUserWarehouse.warehouse_id == warehouse_id,
batch_scope_condition(BizWarehouse.test_batch_id, scope),
)
)
return list(result.scalars().all())
total = 0 @classmethod
for columns in references: async def list_site_assigned_user_ids(
predicates = [column == resource_id for column in columns] cls,
predicate = predicates[0] if len(predicates) == 1 else or_(*predicates) db: AsyncSession,
result = await db.execute(select(func.count('*')).select_from(columns[0].class_).where(predicate)) site_id: int,
total += result.scalar() or 0 scope: TestBatchScope,
return total ) -> list[int]:
result = await db.execute(
select(BizUserSite.user_id)
.join(BizCustomerSite, BizCustomerSite.site_id == BizUserSite.site_id)
.where(
BizUserSite.site_id == site_id,
batch_scope_condition(BizCustomerSite.test_batch_id, scope),
)
)
return list(result.scalars().all())
@@ -32,11 +32,6 @@ class StrainModel(BaseModel):
update_by: str | None = Field(default=None, max_length=64, description='更新者') update_by: str | None = Field(default=None, max_length=64, description='更新者')
update_time: datetime | None = Field(default=None, description='更新时间') update_time: datetime | None = Field(default=None, description='更新时间')
remark: str | None = Field(default=None, max_length=500, description='备注') remark: str | None = Field(default=None, max_length=500, description='备注')
can_remove: bool | None = Field(
default=None,
description='是否允许删除',
json_schema_extra={'readOnly': True},
)
class StrainPageQueryModel(BaseModel): class StrainPageQueryModel(BaseModel):
@@ -91,11 +86,6 @@ class CustomerModel(BaseModel):
update_by: str | None = Field(default=None, max_length=64, description='更新者') update_by: str | None = Field(default=None, max_length=64, description='更新者')
update_time: datetime | None = Field(default=None, description='更新时间') update_time: datetime | None = Field(default=None, description='更新时间')
remark: str | None = Field(default=None, max_length=500, description='备注') remark: str | None = Field(default=None, max_length=500, description='备注')
can_remove: bool | None = Field(
default=None,
description='是否允许删除',
json_schema_extra={'readOnly': True},
)
class CustomerPageQueryModel(BaseModel): class CustomerPageQueryModel(BaseModel):
@@ -158,11 +148,6 @@ class SiteModel(BaseModel):
update_by: str | None = Field(default=None, max_length=64, description='更新者') update_by: str | None = Field(default=None, max_length=64, description='更新者')
update_time: datetime | None = Field(default=None, description='更新时间') update_time: datetime | None = Field(default=None, description='更新时间')
remark: str | None = Field(default=None, max_length=500, description='备注') remark: str | None = Field(default=None, max_length=500, description='备注')
can_remove: bool | None = Field(
default=None,
description='是否允许删除',
json_schema_extra={'readOnly': True},
)
class SitePageQueryModel(BaseModel): class SitePageQueryModel(BaseModel):
@@ -219,11 +204,6 @@ class LineModel(BaseModel):
update_by: str | None = Field(default=None, max_length=64, description='更新者') update_by: str | None = Field(default=None, max_length=64, description='更新者')
update_time: datetime | None = Field(default=None, description='更新时间') update_time: datetime | None = Field(default=None, description='更新时间')
remark: str | None = Field(default=None, max_length=500, description='备注') remark: str | None = Field(default=None, max_length=500, description='备注')
can_remove: bool | None = Field(
default=None,
description='是否允许删除',
json_schema_extra={'readOnly': True},
)
class LinePageQueryModel(BaseModel): class LinePageQueryModel(BaseModel):
@@ -280,11 +260,6 @@ class WarehouseModel(BaseModel):
update_by: str | None = Field(default=None, max_length=64, description='更新者') update_by: str | None = Field(default=None, max_length=64, description='更新者')
update_time: datetime | None = Field(default=None, description='更新时间') update_time: datetime | None = Field(default=None, description='更新时间')
remark: str | None = Field(default=None, max_length=500, description='备注') remark: str | None = Field(default=None, max_length=500, description='备注')
can_remove: bool | None = Field(
default=None,
description='是否允许删除',
json_schema_extra={'readOnly': True},
)
class WarehousePageQueryModel(BaseModel): class WarehousePageQueryModel(BaseModel):
@@ -341,7 +316,6 @@ class PalletModel(BaseModel):
create_time: datetime | None = Field(default=None, description='创建时间') create_time: datetime | None = Field(default=None, description='创建时间')
update_by: str | None = Field(default=None, max_length=64, description='更新者') update_by: str | None = Field(default=None, max_length=64, description='更新者')
update_time: datetime | None = Field(default=None, description='更新时间') update_time: datetime | None = Field(default=None, description='更新时间')
can_remove: bool | None = Field(default=None, description='是否允许删除')
class PalletCreateModel(BaseModel): class PalletCreateModel(BaseModel):
@@ -1,4 +1,4 @@
from typing import Any, Literal from typing import Any, Literal, NoReturn
from pydantic import BaseModel from pydantic import BaseModel
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
@@ -6,7 +6,9 @@ from sqlalchemy.ext.asyncio import AsyncSession
from common.vo import CrudResponseModel, PageModel from common.vo import CrudResponseModel, PageModel
from exceptions.exception import ServiceException, ServiceWarning from exceptions.exception import ServiceException, ServiceWarning
from module_admin.entity.vo.user_vo import CurrentUserModel
from module_business.dao.master_dao import MasterDao from module_business.dao.master_dao import MasterDao
from module_business.domain.test_batch_scope import TestBatchScope, assert_record_in_scope
from module_business.entity.vo.master_vo import ( from module_business.entity.vo.master_vo import (
CustomerModel, CustomerModel,
CustomerPageQueryModel, CustomerPageQueryModel,
@@ -24,6 +26,7 @@ from module_business.entity.vo.master_vo import (
WarehouseModel, WarehouseModel,
WarehousePageQueryModel, WarehousePageQueryModel,
) )
from module_business.service.test_batch_access_service import TestBatchAccessService
MasterResource = Literal['strain', 'customer', 'site', 'line', 'warehouse'] MasterResource = Literal['strain', 'customer', 'site', 'line', 'warehouse']
@@ -71,17 +74,35 @@ class MasterService:
"""菌种、客户、现场、产线和仓库主数据服务层。""" """菌种、客户、现场、产线和仓库主数据服务层。"""
@staticmethod @staticmethod
async def _validate_parent(db: AsyncSession, resource: MasterResource, page_object: BaseModel) -> None: async def _prepare_test_write(
db: AsyncSession,
current_user: CurrentUserModel,
scope: TestBatchScope,
) -> None:
if scope.mode != 'TEST':
return
TestBatchAccessService.assert_super_admin(current_user)
await TestBatchAccessService.lock_active_batch(db, scope)
@staticmethod
async def _validate_parent(
db: AsyncSession,
resource: MasterResource,
page_object: BaseModel,
scope: TestBatchScope,
) -> None:
if resource == 'site': if resource == 'site':
customer = await MasterDao.get_customer(db, page_object.customer_id) customer = await MasterDao.get_customer(db, page_object.customer_id, scope)
if customer is None: if customer is None:
raise ServiceException(message='目标客户不存在') raise ServiceException(message='目标客户不存在')
assert_record_in_scope(customer, scope, '目标客户')
if customer.status != '0': if customer.status != '0':
raise ServiceException(message='目标客户已停用') raise ServiceException(message='目标客户已停用')
elif resource == 'line': elif resource == 'line':
site = await MasterDao.get_site(db, page_object.site_id) site = await MasterDao.get_site(db, page_object.site_id, scope)
if site is None: if site is None:
raise ServiceException(message='目标客户现场不存在') raise ServiceException(message='目标客户现场不存在')
assert_record_in_scope(site, scope, '目标客户现场')
if site.status != '0': if site.status != '0':
raise ServiceException(message='目标客户现场已停用') raise ServiceException(message='目标客户现场已停用')
@@ -127,50 +148,37 @@ class MasterService:
code = getattr(page_object, config['code_field']) code = getattr(page_object, config['code_field'])
raise ServiceException(message=f'{config["label"]}编码{code}已存在') raise ServiceException(message=f'{config["label"]}编码{code}已存在')
@staticmethod
def _extract_ids(resource: MasterResource, value: int | BaseModel) -> list[int]:
ids = [value] if isinstance(value, int) else list(getattr(value, _RESOURCE_CONFIG[resource]['ids_field']))
if not ids or any(resource_id <= 0 for resource_id in ids):
raise ServiceException(message='主数据ID必须为正整数')
if len(ids) != len(set(ids)):
raise ServiceException(message='主数据ID不能重复')
return ids
@classmethod @classmethod
async def _list( async def _list(
cls, cls,
db: AsyncSession, db: AsyncSession,
resource: MasterResource, resource: MasterResource,
query_object: BaseModel, query_object: BaseModel,
scope: TestBatchScope,
is_page: bool, is_page: bool,
) -> PageModel | list[BaseModel]: ) -> PageModel | list[BaseModel]:
result = await getattr(MasterDao, f'list_{resource}')(db, query_object, is_page) result = await getattr(MasterDao, f'list_{resource}')(db, query_object, scope, is_page)
config = _RESOURCE_CONFIG[resource] config = _RESOURCE_CONFIG[resource]
records = result.rows if isinstance(result, PageModel) else result records = result.rows if isinstance(result, PageModel) else result
models = [config['vo'].model_validate(record) for record in records] rows = [config['vo'].model_validate(record) for record in records]
id_field = config['id_field']
removability = await MasterDao.get_master_removability(
db,
resource,
[getattr(model, id_field) for model in models],
)
rows = [
model.model_copy(update={'can_remove': removability.get(getattr(model, id_field), False)})
for model in models
]
if isinstance(result, PageModel): if isinstance(result, PageModel):
return result.model_copy(update={'rows': rows}) return result.model_copy(update={'rows': rows})
return rows return rows
@classmethod @classmethod
async def _detail(cls, db: AsyncSession, resource: MasterResource, resource_id: int) -> BaseModel: async def _detail(
cls,
db: AsyncSession,
resource: MasterResource,
resource_id: int,
scope: TestBatchScope,
) -> BaseModel:
config = _RESOURCE_CONFIG[resource] config = _RESOURCE_CONFIG[resource]
record = await getattr(MasterDao, f'get_{resource}')(db, resource_id) record = await getattr(MasterDao, f'get_{resource}')(db, resource_id, scope)
if record is None: if record is None:
raise ServiceException(message=f'{config["label"]}不存在') raise ServiceException(message=f'{config["label"]}不存在')
model = config['vo'].model_validate(record) assert_record_in_scope(record, scope, config['label'])
removability = await MasterDao.get_master_removability(db, resource, [resource_id]) return config['vo'].model_validate(record)
return model.model_copy(update={'can_remove': removability.get(resource_id, False)})
@classmethod @classmethod
async def _add( async def _add(
@@ -178,13 +186,16 @@ class MasterService:
db: AsyncSession, db: AsyncSession,
resource: MasterResource, resource: MasterResource,
page_object: BaseModel, page_object: BaseModel,
current_user: CurrentUserModel,
scope: TestBatchScope,
) -> CrudResponseModel: ) -> CrudResponseModel:
config = _RESOURCE_CONFIG[resource] config = _RESOURCE_CONFIG[resource]
try: try:
await cls._validate_parent(db, resource, page_object) await cls._prepare_test_write(db, current_user, scope)
await cls._validate_parent(db, resource, page_object, scope)
await cls._validate_unique(db, resource, page_object) await cls._validate_unique(db, resource, page_object)
safe_object = page_object.model_copy(update={config['id_field']: None}) safe_object = page_object.model_copy(update={config['id_field']: None})
await getattr(MasterDao, f'add_{resource}')(db, safe_object) await getattr(MasterDao, f'add_{resource}')(db, safe_object, scope.batch_id)
await db.commit() await db.commit()
return CrudResponseModel(is_success=True, message='新增成功') return CrudResponseModel(is_success=True, message='新增成功')
except IntegrityError: except IntegrityError:
@@ -200,18 +211,27 @@ class MasterService:
db: AsyncSession, db: AsyncSession,
resource: MasterResource, resource: MasterResource,
page_object: BaseModel, page_object: BaseModel,
current_user: CurrentUserModel,
scope: TestBatchScope,
) -> CrudResponseModel: ) -> CrudResponseModel:
config = _RESOURCE_CONFIG[resource] config = _RESOURCE_CONFIG[resource]
try: try:
await cls._prepare_test_write(db, current_user, scope)
resource_id = getattr(page_object, config['id_field']) resource_id = getattr(page_object, config['id_field'])
if resource_id is None: if resource_id is None:
raise ServiceException(message=f'{config["label"]}ID不能为空') raise ServiceException(message=f'{config["label"]}ID不能为空')
current = await getattr(MasterDao, f'get_{resource}')(db, resource_id) current = await getattr(MasterDao, f'get_{resource}')(
db,
resource_id,
scope,
for_update=scope.mode == 'TEST',
)
if current is None: if current is None:
raise ServiceException(message=f'{config["label"]}不存在') raise ServiceException(message=f'{config["label"]}不存在')
await cls._validate_parent(db, resource, page_object) assert_record_in_scope(current, scope, config['label'])
await cls._validate_parent(db, resource, page_object, scope)
await cls._validate_unique(db, resource, page_object, resource_id) await cls._validate_unique(db, resource, page_object, resource_id)
await getattr(MasterDao, f'update_{resource}')(db, page_object) await getattr(MasterDao, f'update_{resource}')(db, page_object, scope)
await db.commit() await db.commit()
return CrudResponseModel(is_success=True, message='更新成功') return CrudResponseModel(is_success=True, message='更新成功')
except IntegrityError: except IntegrityError:
@@ -221,173 +241,216 @@ class MasterService:
await db.rollback() await db.rollback()
raise raise
@classmethod @staticmethod
async def _remove( async def reject_physical_delete() -> NoReturn:
cls, raise ServiceWarning('', '不支持单条物理删除,请通过测试数据批次清理')
db: AsyncSession,
resource: MasterResource,
value: int | BaseModel,
) -> CrudResponseModel:
config = _RESOURCE_CONFIG[resource]
try:
resource_ids = cls._extract_ids(resource, value)
for resource_id in resource_ids:
if await MasterDao.count_references(db, resource, resource_id) > 0:
raise ServiceWarning(message=f'{config["label"]}已被引用,只能停用,不能删除')
if await getattr(MasterDao, f'get_{resource}')(db, resource_id) is None:
raise ServiceException(message=f'{config["label"]}不存在')
for resource_id in resource_ids:
await getattr(MasterDao, f'delete_{resource}')(db, resource_id)
await db.commit()
return CrudResponseModel(is_success=True, message='删除成功')
except IntegrityError:
await db.rollback()
raise ServiceException(message=f'删除{config["label"]}失败,该数据仍被引用') from None
except Exception:
await db.rollback()
raise
@classmethod @classmethod
async def list_strain( async def list_strain(
cls, cls,
db: AsyncSession, db: AsyncSession,
query_object: StrainPageQueryModel, query_object: StrainPageQueryModel,
scope: TestBatchScope,
is_page: bool = True, is_page: bool = True,
) -> PageModel | list[StrainModel]: ) -> PageModel | list[StrainModel]:
return await cls._list(db, 'strain', query_object, is_page) return await cls._list(db, 'strain', query_object, scope, is_page)
@classmethod @classmethod
async def detail_strain(cls, db: AsyncSession, strain_id: int) -> StrainModel: async def detail_strain(cls, db: AsyncSession, strain_id: int, scope: TestBatchScope) -> StrainModel:
return await cls._detail(db, 'strain', strain_id) return await cls._detail(db, 'strain', strain_id, scope)
@classmethod @classmethod
async def add_strain(cls, db: AsyncSession, strain: StrainModel) -> CrudResponseModel: async def add_strain(
return await cls._add(db, 'strain', strain) cls,
db: AsyncSession,
strain: StrainModel,
current_user: CurrentUserModel,
scope: TestBatchScope,
) -> CrudResponseModel:
return await cls._add(db, 'strain', strain, current_user, scope)
@classmethod @classmethod
async def edit_strain(cls, db: AsyncSession, strain: StrainModel) -> CrudResponseModel: async def edit_strain(
return await cls._edit(db, 'strain', strain) cls,
db: AsyncSession,
strain: StrainModel,
current_user: CurrentUserModel,
scope: TestBatchScope,
) -> CrudResponseModel:
return await cls._edit(db, 'strain', strain, current_user, scope)
@classmethod @classmethod
async def remove_strain( async def remove_strain(
cls, cls,
db: AsyncSession, db: AsyncSession,
value: int | DeleteStrainModel, value: int | DeleteStrainModel,
) -> CrudResponseModel: ) -> NoReturn:
return await cls._remove(db, 'strain', value) return await cls.reject_physical_delete()
@classmethod @classmethod
async def list_customer( async def list_customer(
cls, cls,
db: AsyncSession, db: AsyncSession,
query_object: CustomerPageQueryModel, query_object: CustomerPageQueryModel,
scope: TestBatchScope,
is_page: bool = True, is_page: bool = True,
) -> PageModel | list[CustomerModel]: ) -> PageModel | list[CustomerModel]:
return await cls._list(db, 'customer', query_object, is_page) return await cls._list(db, 'customer', query_object, scope, is_page)
@classmethod @classmethod
async def detail_customer(cls, db: AsyncSession, customer_id: int) -> CustomerModel: async def detail_customer(cls, db: AsyncSession, customer_id: int, scope: TestBatchScope) -> CustomerModel:
return await cls._detail(db, 'customer', customer_id) return await cls._detail(db, 'customer', customer_id, scope)
@classmethod @classmethod
async def add_customer(cls, db: AsyncSession, customer: CustomerModel) -> CrudResponseModel: async def add_customer(
return await cls._add(db, 'customer', customer) cls,
db: AsyncSession,
customer: CustomerModel,
current_user: CurrentUserModel,
scope: TestBatchScope,
) -> CrudResponseModel:
return await cls._add(db, 'customer', customer, current_user, scope)
@classmethod @classmethod
async def edit_customer(cls, db: AsyncSession, customer: CustomerModel) -> CrudResponseModel: async def edit_customer(
return await cls._edit(db, 'customer', customer) cls,
db: AsyncSession,
customer: CustomerModel,
current_user: CurrentUserModel,
scope: TestBatchScope,
) -> CrudResponseModel:
return await cls._edit(db, 'customer', customer, current_user, scope)
@classmethod @classmethod
async def remove_customer( async def remove_customer(
cls, cls,
db: AsyncSession, db: AsyncSession,
value: int | DeleteCustomerModel, value: int | DeleteCustomerModel,
) -> CrudResponseModel: ) -> NoReturn:
return await cls._remove(db, 'customer', value) return await cls.reject_physical_delete()
@classmethod @classmethod
async def list_site( async def list_site(
cls, cls,
db: AsyncSession, db: AsyncSession,
query_object: SitePageQueryModel, query_object: SitePageQueryModel,
scope: TestBatchScope,
is_page: bool = True, is_page: bool = True,
) -> PageModel | list[SiteModel]: ) -> PageModel | list[SiteModel]:
return await cls._list(db, 'site', query_object, is_page) return await cls._list(db, 'site', query_object, scope, is_page)
@classmethod @classmethod
async def detail_site(cls, db: AsyncSession, site_id: int) -> SiteModel: async def detail_site(cls, db: AsyncSession, site_id: int, scope: TestBatchScope) -> SiteModel:
return await cls._detail(db, 'site', site_id) return await cls._detail(db, 'site', site_id, scope)
@classmethod @classmethod
async def add_site(cls, db: AsyncSession, site: SiteModel) -> CrudResponseModel: async def add_site(
return await cls._add(db, 'site', site) cls,
db: AsyncSession,
site: SiteModel,
current_user: CurrentUserModel,
scope: TestBatchScope,
) -> CrudResponseModel:
return await cls._add(db, 'site', site, current_user, scope)
@classmethod @classmethod
async def edit_site(cls, db: AsyncSession, site: SiteModel) -> CrudResponseModel: async def edit_site(
return await cls._edit(db, 'site', site) cls,
db: AsyncSession,
site: SiteModel,
current_user: CurrentUserModel,
scope: TestBatchScope,
) -> CrudResponseModel:
return await cls._edit(db, 'site', site, current_user, scope)
@classmethod @classmethod
async def remove_site( async def remove_site(
cls, cls,
db: AsyncSession, db: AsyncSession,
value: int | DeleteSiteModel, value: int | DeleteSiteModel,
) -> CrudResponseModel: ) -> NoReturn:
return await cls._remove(db, 'site', value) return await cls.reject_physical_delete()
@classmethod @classmethod
async def list_line( async def list_line(
cls, cls,
db: AsyncSession, db: AsyncSession,
query_object: LinePageQueryModel, query_object: LinePageQueryModel,
scope: TestBatchScope,
is_page: bool = True, is_page: bool = True,
) -> PageModel | list[LineModel]: ) -> PageModel | list[LineModel]:
return await cls._list(db, 'line', query_object, is_page) return await cls._list(db, 'line', query_object, scope, is_page)
@classmethod @classmethod
async def detail_line(cls, db: AsyncSession, line_id: int) -> LineModel: async def detail_line(cls, db: AsyncSession, line_id: int, scope: TestBatchScope) -> LineModel:
return await cls._detail(db, 'line', line_id) return await cls._detail(db, 'line', line_id, scope)
@classmethod @classmethod
async def add_line(cls, db: AsyncSession, line: LineModel) -> CrudResponseModel: async def add_line(
return await cls._add(db, 'line', line) cls,
db: AsyncSession,
line: LineModel,
current_user: CurrentUserModel,
scope: TestBatchScope,
) -> CrudResponseModel:
return await cls._add(db, 'line', line, current_user, scope)
@classmethod @classmethod
async def edit_line(cls, db: AsyncSession, line: LineModel) -> CrudResponseModel: async def edit_line(
return await cls._edit(db, 'line', line) cls,
db: AsyncSession,
line: LineModel,
current_user: CurrentUserModel,
scope: TestBatchScope,
) -> CrudResponseModel:
return await cls._edit(db, 'line', line, current_user, scope)
@classmethod @classmethod
async def remove_line( async def remove_line(
cls, cls,
db: AsyncSession, db: AsyncSession,
value: int | DeleteLineModel, value: int | DeleteLineModel,
) -> CrudResponseModel: ) -> NoReturn:
return await cls._remove(db, 'line', value) return await cls.reject_physical_delete()
@classmethod @classmethod
async def list_warehouse( async def list_warehouse(
cls, cls,
db: AsyncSession, db: AsyncSession,
query_object: WarehousePageQueryModel, query_object: WarehousePageQueryModel,
scope: TestBatchScope,
is_page: bool = True, is_page: bool = True,
) -> PageModel | list[WarehouseModel]: ) -> PageModel | list[WarehouseModel]:
return await cls._list(db, 'warehouse', query_object, is_page) return await cls._list(db, 'warehouse', query_object, scope, is_page)
@classmethod @classmethod
async def detail_warehouse(cls, db: AsyncSession, warehouse_id: int) -> WarehouseModel: async def detail_warehouse(cls, db: AsyncSession, warehouse_id: int, scope: TestBatchScope) -> WarehouseModel:
return await cls._detail(db, 'warehouse', warehouse_id) return await cls._detail(db, 'warehouse', warehouse_id, scope)
@classmethod @classmethod
async def add_warehouse(cls, db: AsyncSession, warehouse: WarehouseModel) -> CrudResponseModel: async def add_warehouse(
return await cls._add(db, 'warehouse', warehouse) cls,
db: AsyncSession,
warehouse: WarehouseModel,
current_user: CurrentUserModel,
scope: TestBatchScope,
) -> CrudResponseModel:
return await cls._add(db, 'warehouse', warehouse, current_user, scope)
@classmethod @classmethod
async def edit_warehouse(cls, db: AsyncSession, warehouse: WarehouseModel) -> CrudResponseModel: async def edit_warehouse(
return await cls._edit(db, 'warehouse', warehouse) cls,
db: AsyncSession,
warehouse: WarehouseModel,
current_user: CurrentUserModel,
scope: TestBatchScope,
) -> CrudResponseModel:
return await cls._edit(db, 'warehouse', warehouse, current_user, scope)
@classmethod @classmethod
async def remove_warehouse( async def remove_warehouse(
cls, cls,
db: AsyncSession, db: AsyncSession,
value: int | DeleteWarehouseModel, value: int | DeleteWarehouseModel,
) -> CrudResponseModel: ) -> NoReturn:
return await cls._remove(db, 'warehouse', value) return await cls.reject_physical_delete()
@@ -1,16 +1,18 @@
import secrets import secrets
from datetime import datetime from datetime import datetime
from io import BytesIO from io import BytesIO
from typing import Literal from typing import Literal, NoReturn
import qrcode import qrcode
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from common.vo import CrudResponseModel, PageModel from common.vo import CrudResponseModel, PageModel
from exceptions.exception import ServiceException from exceptions.exception import ServiceException, ServiceWarning
from module_admin.entity.vo.user_vo import CurrentUserModel
from module_business.dao.access_dao import AccessDao from module_business.dao.access_dao import AccessDao
from module_business.dao.master_dao import MasterDao from module_business.dao.master_dao import MasterDao
from module_business.domain.test_batch_scope import TestBatchScope, assert_record_in_scope
from module_business.entity.do.master_do import BizPallet from module_business.entity.do.master_do import BizPallet
from module_business.entity.do.operation_do import BizPalletInventory from module_business.entity.do.operation_do import BizPalletInventory
from module_business.entity.vo.master_vo import ( from module_business.entity.vo.master_vo import (
@@ -22,6 +24,7 @@ from module_business.entity.vo.master_vo import (
PalletPageQueryModel, PalletPageQueryModel,
UserAssignModel, UserAssignModel,
) )
from module_business.service.test_batch_access_service import TestBatchAccessService
AssignmentResource = Literal['warehouse', 'site'] AssignmentResource = Literal['warehouse', 'site']
@@ -41,33 +44,43 @@ class PalletService:
qrcode.make(qr_token).save(buffer, format='PNG') qrcode.make(qr_token).save(buffer, format='PNG')
return buffer.getvalue() return buffer.getvalue()
@staticmethod
async def _prepare_test_write(
db: AsyncSession,
current_user: CurrentUserModel,
scope: TestBatchScope,
) -> None:
if scope.mode != 'TEST':
return
TestBatchAccessService.assert_super_admin(current_user)
await TestBatchAccessService.lock_active_batch(db, scope)
@classmethod @classmethod
async def get_pallet_qrcode(cls, db: AsyncSession, pallet_id: int) -> bytes: async def get_pallet_qrcode(cls, db: AsyncSession, pallet_id: int, scope: TestBatchScope) -> bytes:
pallet = await MasterDao.get_pallet(db, pallet_id) pallet = await MasterDao.get_pallet(db, pallet_id, scope)
if pallet is None: if pallet is None:
raise ServiceException(message='托盘不存在') raise ServiceException(message='托盘不存在')
assert_record_in_scope(pallet, scope, '托盘')
return cls.render_qr_png(pallet.qr_token) return cls.render_qr_png(pallet.qr_token)
@staticmethod
def _extract_pallet_ids(value: int | DeletePalletModel) -> list[int]:
pallet_ids = [value] if isinstance(value, int) else list(value.pallet_ids)
if not pallet_ids or any(pallet_id <= 0 for pallet_id in pallet_ids):
raise ServiceException(message='托盘ID必须为正整数')
if len(pallet_ids) != len(set(pallet_ids)):
raise ServiceException(message='托盘ID不能重复')
return sorted(pallet_ids)
@classmethod @classmethod
async def _lock_pallet_inventory(cls, db: AsyncSession, pallet_id: int) -> tuple[BizPallet, BizPalletInventory]: async def _lock_pallet_inventory(
cls,
db: AsyncSession,
pallet_id: int,
scope: TestBatchScope,
) -> tuple[BizPallet, BizPalletInventory]:
"""按库存、托盘顺序加锁,避免与流转事务形成反序死锁。""" """按库存、托盘顺序加锁,避免与流转事务形成反序死锁。"""
inventory = await MasterDao.get_pallet_inventory(db, pallet_id, for_update=True) inventory = await MasterDao.get_pallet_inventory(db, pallet_id, scope, for_update=True)
if inventory is None: if inventory is None:
if await MasterDao.get_pallet(db, pallet_id) is None: if await MasterDao.get_pallet(db, pallet_id, scope) is None:
raise ServiceException(message='托盘不存在') raise ServiceException(message='托盘不存在')
raise ServiceException(message='托盘库存不存在') raise ServiceException(message='托盘库存不存在')
pallet = await MasterDao.get_pallet(db, pallet_id, for_update=True) assert_record_in_scope(inventory, scope, '托盘库存')
pallet = await MasterDao.get_pallet(db, pallet_id, scope, for_update=True)
if pallet is None: if pallet is None:
raise ServiceException(message='托盘不存在') raise ServiceException(message='托盘不存在')
assert_record_in_scope(pallet, scope, '托盘')
return pallet, inventory return pallet, inventory
@classmethod @classmethod
@@ -75,42 +88,41 @@ class PalletService:
cls, cls,
db: AsyncSession, db: AsyncSession,
query_object: PalletPageQueryModel, query_object: PalletPageQueryModel,
scope: TestBatchScope,
is_page: bool = True, is_page: bool = True,
) -> PageModel | list[PalletModel]: ) -> PageModel | list[PalletModel]:
result = await MasterDao.list_pallet(db, query_object, is_page) result = await MasterDao.list_pallet(db, query_object, scope, is_page)
records = result.rows if isinstance(result, PageModel) else result records = result.rows if isinstance(result, PageModel) else result
models = [PalletModel.model_validate(record) for record in records] rows = [PalletModel.model_validate(record) for record in records]
removability = await MasterDao.get_pallet_removability(
db,
[model.pallet_id for model in models],
)
rows = [model.model_copy(update={'can_remove': removability.get(model.pallet_id, False)}) for model in models]
if isinstance(result, PageModel): if isinstance(result, PageModel):
return result.model_copy(update={'rows': rows}) return result.model_copy(update={'rows': rows})
return rows return rows
@classmethod @classmethod
async def detail_pallet(cls, db: AsyncSession, pallet_id: int) -> PalletModel: async def detail_pallet(cls, db: AsyncSession, pallet_id: int, scope: TestBatchScope) -> PalletModel:
pallet = await MasterDao.get_pallet(db, pallet_id) pallet = await MasterDao.get_pallet(db, pallet_id, scope)
if pallet is None: if pallet is None:
raise ServiceException(message='托盘不存在') raise ServiceException(message='托盘不存在')
model = PalletModel.model_validate(pallet) assert_record_in_scope(pallet, scope, '托盘')
removability = await MasterDao.get_pallet_removability(db, [pallet_id]) return PalletModel.model_validate(pallet)
return model.model_copy(update={'can_remove': removability.get(pallet_id, False)})
@classmethod @classmethod
async def add_pallet( async def add_pallet(
cls, cls,
db: AsyncSession, db: AsyncSession,
pallet: PalletCreateModel, pallet: PalletCreateModel,
current_user_name: str, current_user: CurrentUserModel,
scope: TestBatchScope,
) -> CrudResponseModel: ) -> CrudResponseModel:
try: try:
await cls._prepare_test_write(db, current_user, scope)
if await MasterDao.find_pallet_by_code(db, pallet.pallet_code) is not None: if await MasterDao.find_pallet_by_code(db, pallet.pallet_code) is not None:
raise ServiceException(message='托盘编号已存在') raise ServiceException(message='托盘编号已存在')
current_time = datetime.now() current_time = datetime.now()
current_user_name = current_user.user.user_name
pallet_record = BizPallet( pallet_record = BizPallet(
test_batch_id=scope.batch_id,
pallet_code=pallet.pallet_code, pallet_code=pallet.pallet_code,
qr_token=secrets.token_urlsafe(32), qr_token=secrets.token_urlsafe(32),
capacity_packages=pallet.capacity_packages, capacity_packages=pallet.capacity_packages,
@@ -124,6 +136,7 @@ class PalletService:
pallet_record = await MasterDao.add_pallet(db, pallet_record) pallet_record = await MasterDao.add_pallet(db, pallet_record)
inventory = BizPalletInventory( inventory = BizPalletInventory(
pallet_id=pallet_record.pallet_id, pallet_id=pallet_record.pallet_id,
test_batch_id=scope.batch_id,
flow_state='UNSTOCKED', flow_state='UNSTOCKED',
remaining_packages=0, remaining_packages=0,
version=0, version=0,
@@ -144,16 +157,19 @@ class PalletService:
cls, cls,
db: AsyncSession, db: AsyncSession,
pallet: PalletEditModel, pallet: PalletEditModel,
current_user_name: str, current_user: CurrentUserModel,
scope: TestBatchScope,
) -> CrudResponseModel: ) -> CrudResponseModel:
try: try:
_, inventory = await cls._lock_pallet_inventory(db, pallet.pallet_id) await cls._prepare_test_write(db, current_user, scope)
_, inventory = await cls._lock_pallet_inventory(db, pallet.pallet_id, scope)
if pallet.capacity_packages < inventory.remaining_packages: if pallet.capacity_packages < inventory.remaining_packages:
raise ServiceException(message='托盘容量不能小于当前剩余包数') raise ServiceException(message='托盘容量不能小于当前剩余包数')
await MasterDao.update_pallet( await MasterDao.update_pallet(
db, db,
pallet, pallet,
update_by=current_user_name, scope,
update_by=current_user.user.user_name,
update_time=datetime.now(), update_time=datetime.now(),
) )
await db.commit() await db.commit()
@@ -170,27 +186,8 @@ class PalletService:
cls, cls,
db: AsyncSession, db: AsyncSession,
value: int | DeletePalletModel, value: int | DeletePalletModel,
) -> CrudResponseModel: ) -> NoReturn:
try: raise ServiceWarning('', '不支持单条物理删除,请通过测试数据批次清理')
pallet_ids = cls._extract_pallet_ids(value)
for pallet_id in pallet_ids:
await cls._lock_pallet_inventory(db, pallet_id)
removability = await MasterDao.get_pallet_removability(db, pallet_ids)
if not all(removability.get(pallet_id, False) for pallet_id in pallet_ids):
raise ServiceException(message='托盘状态或关联数据不允许删除')
for pallet_id in pallet_ids:
await MasterDao.delete_pallet_inventory(db, pallet_id)
await MasterDao.delete_pallet(db, pallet_id)
await db.commit()
return CrudResponseModel(is_success=True, message='删除成功')
except IntegrityError:
await db.rollback()
raise ServiceException(message='删除托盘失败,托盘状态或关联数据已变化') from None
except Exception:
await db.rollback()
raise
@classmethod @classmethod
async def _list_resource_users( async def _list_resource_users(
@@ -198,12 +195,17 @@ class PalletService:
db: AsyncSession, db: AsyncSession,
resource: AssignmentResource, resource: AssignmentResource,
resource_id: int, resource_id: int,
scope: TestBatchScope,
) -> list[BusinessUserOptionModel]: ) -> list[BusinessUserOptionModel]:
if await getattr(MasterDao, f'get_{resource}')(db, resource_id) is None: record = await getattr(MasterDao, f'get_{resource}')(db, resource_id, scope)
if record is None:
raise ServiceException(message=f'{_ASSIGNMENT_LABELS[resource]}不存在') raise ServiceException(message=f'{_ASSIGNMENT_LABELS[resource]}不存在')
assert_record_in_scope(record, scope, _ASSIGNMENT_LABELS[resource])
users = await MasterDao.list_assignable_users(db) users = await MasterDao.list_assignable_users(db)
assigned_user_ids = set(await getattr(MasterDao, f'list_{resource}_assigned_user_ids')(db, resource_id)) assigned_user_ids = set(
await getattr(MasterDao, f'list_{resource}_assigned_user_ids')(db, resource_id, scope)
)
return [ return [
BusinessUserOptionModel( BusinessUserOptionModel(
user_id=user.user_id, user_id=user.user_id,
@@ -219,16 +221,18 @@ class PalletService:
cls, cls,
db: AsyncSession, db: AsyncSession,
warehouse_id: int, warehouse_id: int,
scope: TestBatchScope,
) -> list[BusinessUserOptionModel]: ) -> list[BusinessUserOptionModel]:
return await cls._list_resource_users(db, 'warehouse', warehouse_id) return await cls._list_resource_users(db, 'warehouse', warehouse_id, scope)
@classmethod @classmethod
async def list_site_users( async def list_site_users(
cls, cls,
db: AsyncSession, db: AsyncSession,
site_id: int, site_id: int,
scope: TestBatchScope,
) -> list[BusinessUserOptionModel]: ) -> list[BusinessUserOptionModel]:
return await cls._list_resource_users(db, 'site', site_id) return await cls._list_resource_users(db, 'site', site_id, scope)
@classmethod @classmethod
async def _assign_resource_users( async def _assign_resource_users(
@@ -237,18 +241,21 @@ class PalletService:
resource: AssignmentResource, resource: AssignmentResource,
resource_id: int, resource_id: int,
assignment: UserAssignModel, assignment: UserAssignModel,
current_user: CurrentUserModel,
scope: TestBatchScope,
) -> CrudResponseModel: ) -> CrudResponseModel:
label = _ASSIGNMENT_LABELS[resource] label = _ASSIGNMENT_LABELS[resource]
try: try:
if ( await cls._prepare_test_write(db, current_user, scope)
await getattr(MasterDao, f'get_{resource}')( record = await getattr(MasterDao, f'get_{resource}')(
db, db,
resource_id, resource_id,
for_update=True, scope,
) for_update=True,
is None )
): if record is None:
raise ServiceException(message=f'{label}不存在') raise ServiceException(message=f'{label}不存在')
assert_record_in_scope(record, scope, label)
users = await MasterDao.list_assignable_users_by_ids(db, assignment.user_ids) users = await MasterDao.list_assignable_users_by_ids(db, assignment.user_ids)
if {user.user_id for user in users} != set(assignment.user_ids): if {user.user_id for user in users} != set(assignment.user_ids):
raise ServiceException(message='存在不存在、停用或已删除的用户') raise ServiceException(message='存在不存在、停用或已删除的用户')
@@ -268,8 +275,17 @@ class PalletService:
db: AsyncSession, db: AsyncSession,
warehouse_id: int, warehouse_id: int,
assignment: UserAssignModel, assignment: UserAssignModel,
current_user: CurrentUserModel,
scope: TestBatchScope,
) -> CrudResponseModel: ) -> CrudResponseModel:
return await cls._assign_resource_users(db, 'warehouse', warehouse_id, assignment) return await cls._assign_resource_users(
db,
'warehouse',
warehouse_id,
assignment,
current_user,
scope,
)
@classmethod @classmethod
async def assign_site_users( async def assign_site_users(
@@ -277,5 +293,14 @@ class PalletService:
db: AsyncSession, db: AsyncSession,
site_id: int, site_id: int,
assignment: UserAssignModel, assignment: UserAssignModel,
current_user: CurrentUserModel,
scope: TestBatchScope,
) -> CrudResponseModel: ) -> CrudResponseModel:
return await cls._assign_resource_users(db, 'site', site_id, assignment) return await cls._assign_resource_users(
db,
'site',
site_id,
assignment,
current_user,
scope,
)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -97,7 +97,6 @@ MASTER_MODEL_FIELDS = {
'update_by', 'update_by',
'update_time', 'update_time',
'remark', 'remark',
'can_remove',
}, },
StrainPageQueryModel: {'page_num', 'page_size', 'strain_code', 'strain_name', 'status'}, StrainPageQueryModel: {'page_num', 'page_size', 'strain_code', 'strain_name', 'status'},
DeleteStrainModel: {'strain_ids'}, DeleteStrainModel: {'strain_ids'},
@@ -111,7 +110,6 @@ MASTER_MODEL_FIELDS = {
'update_by', 'update_by',
'update_time', 'update_time',
'remark', 'remark',
'can_remove',
}, },
CustomerPageQueryModel: {'page_num', 'page_size', 'customer_code', 'customer_name', 'status'}, CustomerPageQueryModel: {'page_num', 'page_size', 'customer_code', 'customer_name', 'status'},
DeleteCustomerModel: {'customer_ids'}, DeleteCustomerModel: {'customer_ids'},
@@ -127,7 +125,6 @@ MASTER_MODEL_FIELDS = {
'update_by', 'update_by',
'update_time', 'update_time',
'remark', 'remark',
'can_remove',
}, },
SitePageQueryModel: {'page_num', 'page_size', 'customer_id', 'site_code', 'site_name', 'status'}, SitePageQueryModel: {'page_num', 'page_size', 'customer_id', 'site_code', 'site_name', 'status'},
DeleteSiteModel: {'site_ids'}, DeleteSiteModel: {'site_ids'},
@@ -142,7 +139,6 @@ MASTER_MODEL_FIELDS = {
'update_by', 'update_by',
'update_time', 'update_time',
'remark', 'remark',
'can_remove',
}, },
LinePageQueryModel: {'page_num', 'page_size', 'site_id', 'line_code', 'line_name', 'status'}, LinePageQueryModel: {'page_num', 'page_size', 'site_id', 'line_code', 'line_name', 'status'},
DeleteLineModel: {'line_ids'}, DeleteLineModel: {'line_ids'},
@@ -157,7 +153,6 @@ MASTER_MODEL_FIELDS = {
'update_by', 'update_by',
'update_time', 'update_time',
'remark', 'remark',
'can_remove',
}, },
WarehousePageQueryModel: {'page_num', 'page_size', 'warehouse_code', 'warehouse_name', 'status'}, WarehousePageQueryModel: {'page_num', 'page_size', 'warehouse_code', 'warehouse_name', 'status'},
DeleteWarehouseModel: {'warehouse_ids'}, DeleteWarehouseModel: {'warehouse_ids'},
@@ -172,7 +167,6 @@ MASTER_MODEL_FIELDS = {
'create_time', 'create_time',
'update_by', 'update_by',
'update_time', 'update_time',
'can_remove',
}, },
PalletCreateModel: {'pallet_code', 'capacity_packages', 'remark'}, PalletCreateModel: {'pallet_code', 'capacity_packages', 'remark'},
PalletEditModel: {'pallet_id', 'capacity_packages', 'asset_status', 'remark'}, PalletEditModel: {'pallet_id', 'capacity_packages', 'asset_status', 'remark'},
@@ -858,6 +852,26 @@ class BusinessVoContractTest(unittest.TestCase):
with self.subTest(model=model_class.__name__): with self.subTest(model=model_class.__name__):
self.assertEqual('forbid', model_class.model_config.get('extra')) self.assertEqual('forbid', model_class.model_config.get('extra'))
def test_master_write_models_reject_untrusted_test_batch_id(self) -> None:
payloads = (
(StrainModel, {'strainCode': 'S-1', 'strainName': '菌种'}),
(CustomerModel, {'customerCode': 'C-1', 'customerName': '客户'}),
(
SiteModel,
{'customerId': 1, 'siteCode': 'SITE-1', 'siteName': '现场'},
),
(LineModel, {'siteId': 1, 'lineCode': 'L-1', 'lineName': '产线'}),
(WarehouseModel, {'warehouseCode': 'W-1', 'warehouseName': '仓库'}),
(PalletCreateModel, {'palletCode': 'P-1', 'capacityPackages': 20}),
(
PalletEditModel,
{'palletId': 1, 'capacityPackages': 20, 'assetStatus': '0'},
),
)
for model_class, payload in payloads:
with self.subTest(model=model_class.__name__), self.assertRaises(ValidationError):
model_class.model_validate(payload | {'testBatchId': 9})
def test_all_test_batch_models_forbid_extra_fields(self) -> None: def test_all_test_batch_models_forbid_extra_fields(self) -> None:
for model_class in TEST_BATCH_MODEL_FIELDS: for model_class in TEST_BATCH_MODEL_FIELDS:
with self.subTest(model=model_class.__name__): with self.subTest(model=model_class.__name__):