安全:隔离主数据并封闭单条删除
This commit is contained in:
@@ -12,6 +12,8 @@ from common.enums import BusinessType
|
||||
from common.router import APIRouterPro
|
||||
from common.vo import DataResponseModel, PageResponseModel, ResponseBaseModel
|
||||
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.service.master_service import MasterService
|
||||
from utils.response_util import ResponseUtil
|
||||
@@ -32,8 +34,9 @@ async def get_master_customer_list(
|
||||
request: Request,
|
||||
customer_page_query: Annotated[CustomerPageQueryModel, Query()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
scope: Annotated[TestBatchScope, TestBatchScopeDependency()],
|
||||
) -> 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)
|
||||
|
||||
@@ -49,8 +52,9 @@ async def query_detail_master_customer(
|
||||
request: Request,
|
||||
customer_id: Annotated[int, Path(gt=0, description='客户ID')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
scope: Annotated[TestBatchScope, TestBatchScopeDependency()],
|
||||
) -> 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)
|
||||
|
||||
@@ -68,13 +72,14 @@ async def add_master_customer(
|
||||
add_customer: CustomerModel,
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
scope: Annotated[TestBatchScope, TestBatchScopeDependency(write=True)],
|
||||
) -> Response:
|
||||
current_time = datetime.now()
|
||||
add_customer.create_by = current_user.user.user_name
|
||||
add_customer.create_time = current_time
|
||||
add_customer.update_by = current_user.user.user_name
|
||||
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)
|
||||
|
||||
@@ -92,10 +97,11 @@ async def edit_master_customer(
|
||||
edit_customer: CustomerModel,
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
scope: Annotated[TestBatchScope, TestBatchScopeDependency(write=True)],
|
||||
) -> Response:
|
||||
edit_customer.update_by = current_user.user.user_name
|
||||
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)
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ from common.enums import BusinessType
|
||||
from common.router import APIRouterPro
|
||||
from common.vo import DataResponseModel, PageResponseModel, ResponseBaseModel
|
||||
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.service.master_service import MasterService
|
||||
from utils.response_util import ResponseUtil
|
||||
@@ -30,8 +32,9 @@ async def get_master_line_list(
|
||||
request: Request,
|
||||
line_page_query: Annotated[LinePageQueryModel, Query()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
scope: Annotated[TestBatchScope, TestBatchScopeDependency()],
|
||||
) -> 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)
|
||||
|
||||
@@ -47,8 +50,9 @@ async def query_detail_master_line(
|
||||
request: Request,
|
||||
line_id: Annotated[int, Path(gt=0, description='生产线ID')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
scope: Annotated[TestBatchScope, TestBatchScopeDependency()],
|
||||
) -> 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)
|
||||
|
||||
@@ -66,13 +70,14 @@ async def add_master_line(
|
||||
add_line: LineModel,
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
scope: Annotated[TestBatchScope, TestBatchScopeDependency(write=True)],
|
||||
) -> Response:
|
||||
current_time = datetime.now()
|
||||
add_line.create_by = current_user.user.user_name
|
||||
add_line.create_time = current_time
|
||||
add_line.update_by = current_user.user.user_name
|
||||
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)
|
||||
|
||||
@@ -90,10 +95,11 @@ async def edit_master_line(
|
||||
edit_line: LineModel,
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
scope: Annotated[TestBatchScope, TestBatchScopeDependency(write=True)],
|
||||
) -> Response:
|
||||
edit_line.update_by = current_user.user.user_name
|
||||
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)
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ from common.enums import BusinessType
|
||||
from common.router import APIRouterPro
|
||||
from common.vo import DataResponseModel, PageResponseModel, ResponseBaseModel
|
||||
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 (
|
||||
DeletePalletModel,
|
||||
PalletCreateModel,
|
||||
@@ -37,8 +39,9 @@ async def get_master_pallet_list(
|
||||
request: Request,
|
||||
pallet_page_query: Annotated[PalletPageQueryModel, Query()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
scope: Annotated[TestBatchScope, TestBatchScopeDependency()],
|
||||
) -> 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)
|
||||
|
||||
|
||||
@@ -54,8 +57,9 @@ async def query_master_pallet_qrcode(
|
||||
request: Request,
|
||||
pallet_id: Annotated[int, Path(gt=0, description='托盘ID')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
scope: Annotated[TestBatchScope, TestBatchScopeDependency()],
|
||||
) -> 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')
|
||||
|
||||
|
||||
@@ -70,8 +74,9 @@ async def query_detail_master_pallet(
|
||||
request: Request,
|
||||
pallet_id: Annotated[int, Path(gt=0, description='托盘ID')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
scope: Annotated[TestBatchScope, TestBatchScopeDependency()],
|
||||
) -> 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)
|
||||
|
||||
|
||||
@@ -88,8 +93,9 @@ async def add_master_pallet(
|
||||
add_pallet: PalletCreateModel,
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
scope: Annotated[TestBatchScope, TestBatchScopeDependency(write=True)],
|
||||
) -> 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)
|
||||
|
||||
|
||||
@@ -106,8 +112,9 @@ async def edit_master_pallet(
|
||||
edit_pallet: PalletEditModel,
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
scope: Annotated[TestBatchScope, TestBatchScopeDependency(write=True)],
|
||||
) -> 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)
|
||||
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ from common.enums import BusinessType
|
||||
from common.router import APIRouterPro
|
||||
from common.vo import DataResponseModel, PageResponseModel, ResponseBaseModel
|
||||
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 (
|
||||
BusinessUserOptionModel,
|
||||
DeleteSiteModel,
|
||||
@@ -45,8 +47,9 @@ async def get_master_site_users(
|
||||
request: Request,
|
||||
site_id: Annotated[int, Path(gt=0, description='客户现场ID')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
scope: Annotated[TestBatchScope, TestBatchScopeDependency()],
|
||||
) -> 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)
|
||||
|
||||
|
||||
@@ -64,8 +67,9 @@ async def assign_master_site_users(
|
||||
assignment: UserAssignModel,
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, ASSIGN_CURRENT_USER()],
|
||||
scope: Annotated[TestBatchScope, TestBatchScopeDependency(write=True)],
|
||||
) -> 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)
|
||||
|
||||
|
||||
@@ -80,8 +84,9 @@ async def get_master_site_list(
|
||||
request: Request,
|
||||
site_page_query: Annotated[SitePageQueryModel, Query()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
scope: Annotated[TestBatchScope, TestBatchScopeDependency()],
|
||||
) -> 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)
|
||||
|
||||
@@ -97,8 +102,9 @@ async def query_detail_master_site(
|
||||
request: Request,
|
||||
site_id: Annotated[int, Path(gt=0, description='客户现场ID')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
scope: Annotated[TestBatchScope, TestBatchScopeDependency()],
|
||||
) -> 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)
|
||||
|
||||
@@ -116,13 +122,14 @@ async def add_master_site(
|
||||
add_site: SiteModel,
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
scope: Annotated[TestBatchScope, TestBatchScopeDependency(write=True)],
|
||||
) -> Response:
|
||||
current_time = datetime.now()
|
||||
add_site.create_by = current_user.user.user_name
|
||||
add_site.create_time = current_time
|
||||
add_site.update_by = current_user.user.user_name
|
||||
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)
|
||||
|
||||
@@ -140,10 +147,11 @@ async def edit_master_site(
|
||||
edit_site: SiteModel,
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
scope: Annotated[TestBatchScope, TestBatchScopeDependency(write=True)],
|
||||
) -> Response:
|
||||
edit_site.update_by = current_user.user.user_name
|
||||
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)
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ from common.enums import BusinessType
|
||||
from common.router import APIRouterPro
|
||||
from common.vo import DataResponseModel, PageResponseModel, ResponseBaseModel
|
||||
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.service.master_service import MasterService
|
||||
from utils.response_util import ResponseUtil
|
||||
@@ -32,8 +34,9 @@ async def get_master_strain_list(
|
||||
request: Request,
|
||||
strain_page_query: Annotated[StrainPageQueryModel, Query()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
scope: Annotated[TestBatchScope, TestBatchScopeDependency()],
|
||||
) -> 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)
|
||||
|
||||
@@ -49,8 +52,9 @@ async def query_detail_master_strain(
|
||||
request: Request,
|
||||
strain_id: Annotated[int, Path(gt=0, description='菌种ID')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
scope: Annotated[TestBatchScope, TestBatchScopeDependency()],
|
||||
) -> 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)
|
||||
|
||||
@@ -68,13 +72,14 @@ async def add_master_strain(
|
||||
add_strain: StrainModel,
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
scope: Annotated[TestBatchScope, TestBatchScopeDependency(write=True)],
|
||||
) -> Response:
|
||||
current_time = datetime.now()
|
||||
add_strain.create_by = current_user.user.user_name
|
||||
add_strain.create_time = current_time
|
||||
add_strain.update_by = current_user.user.user_name
|
||||
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)
|
||||
|
||||
@@ -92,10 +97,11 @@ async def edit_master_strain(
|
||||
edit_strain: StrainModel,
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
scope: Annotated[TestBatchScope, TestBatchScopeDependency(write=True)],
|
||||
) -> Response:
|
||||
edit_strain.update_by = current_user.user.user_name
|
||||
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)
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ from common.enums import BusinessType
|
||||
from common.router import APIRouterPro
|
||||
from common.vo import DataResponseModel, PageResponseModel, ResponseBaseModel
|
||||
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 (
|
||||
BusinessUserOptionModel,
|
||||
DeleteWarehouseModel,
|
||||
@@ -45,8 +47,9 @@ async def get_master_warehouse_users(
|
||||
request: Request,
|
||||
warehouse_id: Annotated[int, Path(gt=0, description='仓库ID')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
scope: Annotated[TestBatchScope, TestBatchScopeDependency()],
|
||||
) -> 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)
|
||||
|
||||
|
||||
@@ -64,8 +67,9 @@ async def assign_master_warehouse_users(
|
||||
assignment: UserAssignModel,
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, ASSIGN_CURRENT_USER()],
|
||||
scope: Annotated[TestBatchScope, TestBatchScopeDependency(write=True)],
|
||||
) -> 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)
|
||||
|
||||
|
||||
@@ -80,8 +84,14 @@ async def get_master_warehouse_list(
|
||||
request: Request,
|
||||
warehouse_page_query: Annotated[WarehousePageQueryModel, Query()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
scope: Annotated[TestBatchScope, TestBatchScopeDependency()],
|
||||
) -> 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)
|
||||
|
||||
@@ -97,8 +107,9 @@ async def query_detail_master_warehouse(
|
||||
request: Request,
|
||||
warehouse_id: Annotated[int, Path(gt=0, description='仓库ID')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
scope: Annotated[TestBatchScope, TestBatchScopeDependency()],
|
||||
) -> 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)
|
||||
|
||||
@@ -116,13 +127,14 @@ async def add_master_warehouse(
|
||||
add_warehouse: WarehouseModel,
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
scope: Annotated[TestBatchScope, TestBatchScopeDependency(write=True)],
|
||||
) -> Response:
|
||||
current_time = datetime.now()
|
||||
add_warehouse.create_by = current_user.user.user_name
|
||||
add_warehouse.create_time = current_time
|
||||
add_warehouse.update_by = current_user.user.user_name
|
||||
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)
|
||||
|
||||
@@ -140,10 +152,11 @@ async def edit_master_warehouse(
|
||||
edit_warehouse: WarehouseModel,
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
scope: Annotated[TestBatchScope, TestBatchScopeDependency(write=True)],
|
||||
) -> Response:
|
||||
edit_warehouse.update_by = current_user.user.user_name
|
||||
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)
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
from datetime import datetime
|
||||
from typing import Any, Literal
|
||||
from typing import Any
|
||||
|
||||
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.orm.attributes import InstrumentedAttribute
|
||||
|
||||
from common.vo import PageModel
|
||||
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 (
|
||||
BizCustomer,
|
||||
BizCustomerSite,
|
||||
@@ -19,18 +19,7 @@ from module_business.entity.do.master_do import (
|
||||
BizUserWarehouse,
|
||||
BizWarehouse,
|
||||
)
|
||||
from module_business.entity.do.operation_do import (
|
||||
BizFillingRecord,
|
||||
BizInboundItem,
|
||||
BizInboundOrder,
|
||||
BizPalletEvent,
|
||||
BizPalletInventory,
|
||||
BizReturnItem,
|
||||
BizReturnOrder,
|
||||
BizShipmentItem,
|
||||
BizShipmentOrder,
|
||||
BizUsageRecord,
|
||||
)
|
||||
from module_business.entity.do.operation_do import BizPalletInventory
|
||||
from module_business.entity.vo.master_vo import (
|
||||
CustomerModel,
|
||||
CustomerPageQueryModel,
|
||||
@@ -47,54 +36,6 @@ from module_business.entity.vo.master_vo import (
|
||||
)
|
||||
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:
|
||||
"""菌种、客户、现场、产线和仓库主数据数据库操作层。"""
|
||||
@@ -120,10 +61,11 @@ class MasterDao:
|
||||
model: type[Any],
|
||||
id_column: InstrumentedAttribute,
|
||||
resource_id: int,
|
||||
scope_predicate: ColumnElement[bool],
|
||||
*,
|
||||
for_update: bool = False,
|
||||
) -> Any | None:
|
||||
statement = select(model).where(id_column == resource_id)
|
||||
statement = select(model).where(id_column == resource_id, scope_predicate)
|
||||
if for_update:
|
||||
statement = statement.with_for_update().execution_options(populate_existing=True)
|
||||
result = await db.execute(statement)
|
||||
@@ -146,8 +88,10 @@ class MasterDao:
|
||||
model: type[Any],
|
||||
id_field: str,
|
||||
page_object: BaseModel,
|
||||
test_batch_id: int | None,
|
||||
) -> 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)
|
||||
db.add(record)
|
||||
await db.flush()
|
||||
@@ -160,63 +104,31 @@ class MasterDao:
|
||||
id_column: InstrumentedAttribute,
|
||||
id_field: str,
|
||||
page_object: BaseModel,
|
||||
scope: TestBatchScope,
|
||||
) -> None:
|
||||
payload = page_object.model_dump(
|
||||
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)
|
||||
await db.execute(update(model).where(id_column == resource_id).values(**payload))
|
||||
|
||||
@staticmethod
|
||||
async def _delete(
|
||||
db: AsyncSession,
|
||||
model: type[Any],
|
||||
id_column: InstrumentedAttribute,
|
||||
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
|
||||
await db.execute(
|
||||
update(model)
|
||||
.where(
|
||||
id_column == resource_id,
|
||||
batch_scope_condition(model.test_batch_id, scope),
|
||||
)
|
||||
.values(**payload)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def list_strain(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
query_object: StrainPageQueryModel,
|
||||
scope: TestBatchScope,
|
||||
is_page: bool = True,
|
||||
) -> PageModel | list[dict[str, Any]]:
|
||||
conditions = []
|
||||
conditions = [batch_scope_condition(BizStrain.test_batch_id, scope)]
|
||||
if query_object.strain_code:
|
||||
conditions.append(BizStrain.strain_code.like(f'%{query_object.strain_code}%'))
|
||||
if query_object.strain_name:
|
||||
@@ -231,6 +143,7 @@ class MasterDao:
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
strain_id: int,
|
||||
scope: TestBatchScope,
|
||||
*,
|
||||
for_update: bool = False,
|
||||
) -> BizStrain | None:
|
||||
@@ -239,6 +152,7 @@ class MasterDao:
|
||||
BizStrain,
|
||||
BizStrain.strain_id,
|
||||
strain_id,
|
||||
batch_scope_condition(BizStrain.test_batch_id, scope),
|
||||
for_update=for_update,
|
||||
)
|
||||
|
||||
@@ -247,25 +161,22 @@ class MasterDao:
|
||||
return await cls._find(db, BizStrain, BizStrain.strain_code, strain_code)
|
||||
|
||||
@classmethod
|
||||
async def add_strain(cls, db: AsyncSession, strain: StrainModel) -> BizStrain:
|
||||
return await cls._add(db, BizStrain, 'strain_id', strain)
|
||||
async def add_strain(cls, db: AsyncSession, strain: StrainModel, test_batch_id: int | None) -> BizStrain:
|
||||
return await cls._add(db, BizStrain, 'strain_id', strain, test_batch_id)
|
||||
|
||||
@classmethod
|
||||
async def update_strain(cls, db: AsyncSession, strain: StrainModel) -> None:
|
||||
await cls._update(db, BizStrain, BizStrain.strain_id, 'strain_id', strain)
|
||||
|
||||
@classmethod
|
||||
async def delete_strain(cls, db: AsyncSession, strain_id: int) -> None:
|
||||
await cls._delete(db, BizStrain, BizStrain.strain_id, strain_id)
|
||||
async def update_strain(cls, db: AsyncSession, strain: StrainModel, scope: TestBatchScope) -> None:
|
||||
await cls._update(db, BizStrain, BizStrain.strain_id, 'strain_id', strain, scope)
|
||||
|
||||
@classmethod
|
||||
async def list_customer(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
query_object: CustomerPageQueryModel,
|
||||
scope: TestBatchScope,
|
||||
is_page: bool = True,
|
||||
) -> PageModel | list[dict[str, Any]]:
|
||||
conditions = []
|
||||
conditions = [batch_scope_condition(BizCustomer.test_batch_id, scope)]
|
||||
if query_object.customer_code:
|
||||
conditions.append(BizCustomer.customer_code.like(f'%{query_object.customer_code}%'))
|
||||
if query_object.customer_name:
|
||||
@@ -276,33 +187,44 @@ class MasterDao:
|
||||
return await cls._paginate(db, statement, query_object, is_page)
|
||||
|
||||
@classmethod
|
||||
async def get_customer(cls, db: AsyncSession, customer_id: int) -> BizCustomer | None:
|
||||
return await cls._get(db, BizCustomer, BizCustomer.customer_id, customer_id)
|
||||
async def get_customer(
|
||||
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
|
||||
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)
|
||||
|
||||
@classmethod
|
||||
async def add_customer(cls, db: AsyncSession, customer: CustomerModel) -> BizCustomer:
|
||||
return await cls._add(db, BizCustomer, 'customer_id', customer)
|
||||
async def add_customer(cls, db: AsyncSession, customer: CustomerModel, test_batch_id: int | None) -> BizCustomer:
|
||||
return await cls._add(db, BizCustomer, 'customer_id', customer, test_batch_id)
|
||||
|
||||
@classmethod
|
||||
async def update_customer(cls, db: AsyncSession, customer: CustomerModel) -> None:
|
||||
await cls._update(db, BizCustomer, BizCustomer.customer_id, 'customer_id', customer)
|
||||
|
||||
@classmethod
|
||||
async def delete_customer(cls, db: AsyncSession, customer_id: int) -> None:
|
||||
await cls._delete(db, BizCustomer, BizCustomer.customer_id, customer_id)
|
||||
async def update_customer(cls, db: AsyncSession, customer: CustomerModel, scope: TestBatchScope) -> None:
|
||||
await cls._update(db, BizCustomer, BizCustomer.customer_id, 'customer_id', customer, scope)
|
||||
|
||||
@classmethod
|
||||
async def list_site(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
query_object: SitePageQueryModel,
|
||||
scope: TestBatchScope,
|
||||
is_page: bool = True,
|
||||
) -> PageModel | list[dict[str, Any]]:
|
||||
conditions = []
|
||||
conditions = [batch_scope_condition(BizCustomerSite.test_batch_id, scope)]
|
||||
if query_object.customer_id:
|
||||
conditions.append(BizCustomerSite.customer_id == query_object.customer_id)
|
||||
if query_object.site_code:
|
||||
@@ -319,6 +241,7 @@ class MasterDao:
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
site_id: int,
|
||||
scope: TestBatchScope,
|
||||
*,
|
||||
for_update: bool = False,
|
||||
) -> BizCustomerSite | None:
|
||||
@@ -327,6 +250,7 @@ class MasterDao:
|
||||
BizCustomerSite,
|
||||
BizCustomerSite.site_id,
|
||||
site_id,
|
||||
batch_scope_condition(BizCustomerSite.test_batch_id, scope),
|
||||
for_update=for_update,
|
||||
)
|
||||
|
||||
@@ -346,25 +270,22 @@ class MasterDao:
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def add_site(cls, db: AsyncSession, site: SiteModel) -> BizCustomerSite:
|
||||
return await cls._add(db, BizCustomerSite, 'site_id', site)
|
||||
async def add_site(cls, db: AsyncSession, site: SiteModel, test_batch_id: int | None) -> BizCustomerSite:
|
||||
return await cls._add(db, BizCustomerSite, 'site_id', site, test_batch_id)
|
||||
|
||||
@classmethod
|
||||
async def update_site(cls, db: AsyncSession, site: SiteModel) -> None:
|
||||
await cls._update(db, BizCustomerSite, BizCustomerSite.site_id, 'site_id', site)
|
||||
|
||||
@classmethod
|
||||
async def delete_site(cls, db: AsyncSession, site_id: int) -> None:
|
||||
await cls._delete(db, BizCustomerSite, BizCustomerSite.site_id, site_id)
|
||||
async def update_site(cls, db: AsyncSession, site: SiteModel, scope: TestBatchScope) -> None:
|
||||
await cls._update(db, BizCustomerSite, BizCustomerSite.site_id, 'site_id', site, scope)
|
||||
|
||||
@classmethod
|
||||
async def list_line(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
query_object: LinePageQueryModel,
|
||||
scope: TestBatchScope,
|
||||
is_page: bool = True,
|
||||
) -> PageModel | list[dict[str, Any]]:
|
||||
conditions = []
|
||||
conditions = [batch_scope_condition(BizProductionLine.test_batch_id, scope)]
|
||||
if query_object.site_id:
|
||||
conditions.append(BizProductionLine.site_id == query_object.site_id)
|
||||
if query_object.line_code:
|
||||
@@ -381,6 +302,7 @@ class MasterDao:
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
line_id: int,
|
||||
scope: TestBatchScope,
|
||||
*,
|
||||
for_update: bool = False,
|
||||
) -> BizProductionLine | None:
|
||||
@@ -389,6 +311,7 @@ class MasterDao:
|
||||
BizProductionLine,
|
||||
BizProductionLine.line_id,
|
||||
line_id,
|
||||
batch_scope_condition(BizProductionLine.test_batch_id, scope),
|
||||
for_update=for_update,
|
||||
)
|
||||
|
||||
@@ -408,25 +331,22 @@ class MasterDao:
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def add_line(cls, db: AsyncSession, line: LineModel) -> BizProductionLine:
|
||||
return await cls._add(db, BizProductionLine, 'line_id', line)
|
||||
async def add_line(cls, db: AsyncSession, line: LineModel, test_batch_id: int | None) -> BizProductionLine:
|
||||
return await cls._add(db, BizProductionLine, 'line_id', line, test_batch_id)
|
||||
|
||||
@classmethod
|
||||
async def update_line(cls, db: AsyncSession, line: LineModel) -> None:
|
||||
await cls._update(db, BizProductionLine, BizProductionLine.line_id, 'line_id', line)
|
||||
|
||||
@classmethod
|
||||
async def delete_line(cls, db: AsyncSession, line_id: int) -> None:
|
||||
await cls._delete(db, BizProductionLine, BizProductionLine.line_id, line_id)
|
||||
async def update_line(cls, db: AsyncSession, line: LineModel, scope: TestBatchScope) -> None:
|
||||
await cls._update(db, BizProductionLine, BizProductionLine.line_id, 'line_id', line, scope)
|
||||
|
||||
@classmethod
|
||||
async def list_warehouse(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
query_object: WarehousePageQueryModel,
|
||||
scope: TestBatchScope,
|
||||
is_page: bool = True,
|
||||
) -> PageModel | list[dict[str, Any]]:
|
||||
conditions = []
|
||||
conditions = [batch_scope_condition(BizWarehouse.test_batch_id, scope)]
|
||||
if query_object.warehouse_code:
|
||||
conditions.append(BizWarehouse.warehouse_code.like(f'%{query_object.warehouse_code}%'))
|
||||
if query_object.warehouse_name:
|
||||
@@ -441,6 +361,7 @@ class MasterDao:
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
warehouse_id: int,
|
||||
scope: TestBatchScope,
|
||||
*,
|
||||
for_update: bool = False,
|
||||
) -> BizWarehouse | None:
|
||||
@@ -449,6 +370,7 @@ class MasterDao:
|
||||
BizWarehouse,
|
||||
BizWarehouse.warehouse_id,
|
||||
warehouse_id,
|
||||
batch_scope_condition(BizWarehouse.test_batch_id, scope),
|
||||
for_update=for_update,
|
||||
)
|
||||
|
||||
@@ -457,25 +379,22 @@ class MasterDao:
|
||||
return await cls._find(db, BizWarehouse, BizWarehouse.warehouse_code, warehouse_code)
|
||||
|
||||
@classmethod
|
||||
async def add_warehouse(cls, db: AsyncSession, warehouse: WarehouseModel) -> BizWarehouse:
|
||||
return await cls._add(db, BizWarehouse, 'warehouse_id', warehouse)
|
||||
async def add_warehouse(cls, db: AsyncSession, warehouse: WarehouseModel, test_batch_id: int | None) -> BizWarehouse:
|
||||
return await cls._add(db, BizWarehouse, 'warehouse_id', warehouse, test_batch_id)
|
||||
|
||||
@classmethod
|
||||
async def update_warehouse(cls, db: AsyncSession, warehouse: WarehouseModel) -> None:
|
||||
await cls._update(db, BizWarehouse, BizWarehouse.warehouse_id, 'warehouse_id', warehouse)
|
||||
|
||||
@classmethod
|
||||
async def delete_warehouse(cls, db: AsyncSession, warehouse_id: int) -> None:
|
||||
await cls._delete(db, BizWarehouse, BizWarehouse.warehouse_id, warehouse_id)
|
||||
async def update_warehouse(cls, db: AsyncSession, warehouse: WarehouseModel, scope: TestBatchScope) -> None:
|
||||
await cls._update(db, BizWarehouse, BizWarehouse.warehouse_id, 'warehouse_id', warehouse, scope)
|
||||
|
||||
@classmethod
|
||||
async def list_pallet(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
query_object: PalletPageQueryModel,
|
||||
scope: TestBatchScope,
|
||||
is_page: bool = True,
|
||||
) -> PageModel | list[dict[str, Any]]:
|
||||
conditions = []
|
||||
conditions = [batch_scope_condition(BizPallet.test_batch_id, scope)]
|
||||
if query_object.pallet_code:
|
||||
conditions.append(BizPallet.pallet_code.like(f'%{query_object.pallet_code}%'))
|
||||
if query_object.asset_status:
|
||||
@@ -488,45 +407,18 @@ class MasterDao:
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
pallet_id: int,
|
||||
scope: TestBatchScope,
|
||||
*,
|
||||
for_update: bool = False,
|
||||
) -> BizPallet | None:
|
||||
statement = select(BizPallet).where(BizPallet.pallet_id == pallet_id)
|
||||
if for_update:
|
||||
statement = statement.with_for_update()
|
||||
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(
|
||||
return await cls._get(
|
||||
db,
|
||||
BizPallet,
|
||||
BizPallet.pallet_id,
|
||||
and_(has_unstocked_inventory, *has_no_business_references).label('can_remove'),
|
||||
).where(BizPallet.pallet_id.in_(pallet_ids))
|
||||
result = await db.execute(statement)
|
||||
for pallet_id, can_remove in result.all():
|
||||
removability[pallet_id] = bool(can_remove)
|
||||
return removability
|
||||
pallet_id,
|
||||
batch_scope_condition(BizPallet.test_batch_id, scope),
|
||||
for_update=for_update,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def find_pallet_by_code(cls, db: AsyncSession, pallet_code: str) -> BizPallet | None:
|
||||
@@ -543,12 +435,16 @@ class MasterDao:
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
pallet: PalletEditModel,
|
||||
scope: TestBatchScope,
|
||||
update_by: str,
|
||||
update_time: datetime,
|
||||
) -> None:
|
||||
await db.execute(
|
||||
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(
|
||||
capacity_packages=pallet.capacity_packages,
|
||||
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
|
||||
async def get_pallet_inventory(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
pallet_id: int,
|
||||
scope: TestBatchScope,
|
||||
*,
|
||||
for_update: bool = False,
|
||||
) -> 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:
|
||||
statement = statement.with_for_update()
|
||||
statement = statement.with_for_update().execution_options(populate_existing=True)
|
||||
result = await db.execute(statement)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
@@ -586,17 +482,6 @@ class MasterDao:
|
||||
await db.flush()
|
||||
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
|
||||
async def list_assignable_users(cls, db: AsyncSession) -> list[Any]:
|
||||
result = await db.execute(
|
||||
@@ -622,31 +507,35 @@ class MasterDao:
|
||||
return list(result.all())
|
||||
|
||||
@classmethod
|
||||
async def list_warehouse_assigned_user_ids(cls, db: AsyncSession, warehouse_id: int) -> list[int]:
|
||||
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(
|
||||
async def list_warehouse_assigned_user_ids(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
resource_type: MasterResource,
|
||||
resource_id: int,
|
||||
) -> int:
|
||||
"""统计主数据的所有直接外键引用;同表的多个位置字段按一行计数。"""
|
||||
references = _REFERENCE_COLUMNS.get(resource_type)
|
||||
if references is None:
|
||||
raise ValueError(f'不支持的主数据类型:{resource_type}')
|
||||
warehouse_id: int,
|
||||
scope: TestBatchScope,
|
||||
) -> list[int]:
|
||||
result = await db.execute(
|
||||
select(BizUserWarehouse.user_id)
|
||||
.join(BizWarehouse, BizWarehouse.warehouse_id == BizUserWarehouse.warehouse_id)
|
||||
.where(
|
||||
BizUserWarehouse.warehouse_id == warehouse_id,
|
||||
batch_scope_condition(BizWarehouse.test_batch_id, scope),
|
||||
)
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
|
||||
total = 0
|
||||
for columns in references:
|
||||
predicates = [column == resource_id for column in columns]
|
||||
predicate = predicates[0] if len(predicates) == 1 else or_(*predicates)
|
||||
result = await db.execute(select(func.count('*')).select_from(columns[0].class_).where(predicate))
|
||||
total += result.scalar() or 0
|
||||
return total
|
||||
@classmethod
|
||||
async def list_site_assigned_user_ids(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
site_id: int,
|
||||
scope: TestBatchScope,
|
||||
) -> 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_time: datetime | None = Field(default=None, 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):
|
||||
@@ -91,11 +86,6 @@ class CustomerModel(BaseModel):
|
||||
update_by: str | None = Field(default=None, max_length=64, description='更新者')
|
||||
update_time: datetime | None = Field(default=None, 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):
|
||||
@@ -158,11 +148,6 @@ class SiteModel(BaseModel):
|
||||
update_by: str | None = Field(default=None, max_length=64, description='更新者')
|
||||
update_time: datetime | None = Field(default=None, 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):
|
||||
@@ -219,11 +204,6 @@ class LineModel(BaseModel):
|
||||
update_by: str | None = Field(default=None, max_length=64, description='更新者')
|
||||
update_time: datetime | None = Field(default=None, 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):
|
||||
@@ -280,11 +260,6 @@ class WarehouseModel(BaseModel):
|
||||
update_by: str | None = Field(default=None, max_length=64, description='更新者')
|
||||
update_time: datetime | None = Field(default=None, 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):
|
||||
@@ -341,7 +316,6 @@ class PalletModel(BaseModel):
|
||||
create_time: datetime | None = Field(default=None, description='创建时间')
|
||||
update_by: str | None = Field(default=None, max_length=64, description='更新者')
|
||||
update_time: datetime | None = Field(default=None, description='更新时间')
|
||||
can_remove: bool | None = Field(default=None, description='是否允许删除')
|
||||
|
||||
|
||||
class PalletCreateModel(BaseModel):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Any, Literal
|
||||
from typing import Any, Literal, NoReturn
|
||||
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
@@ -6,7 +6,9 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from common.vo import CrudResponseModel, PageModel
|
||||
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.domain.test_batch_scope import TestBatchScope, assert_record_in_scope
|
||||
from module_business.entity.vo.master_vo import (
|
||||
CustomerModel,
|
||||
CustomerPageQueryModel,
|
||||
@@ -24,6 +26,7 @@ from module_business.entity.vo.master_vo import (
|
||||
WarehouseModel,
|
||||
WarehousePageQueryModel,
|
||||
)
|
||||
from module_business.service.test_batch_access_service import TestBatchAccessService
|
||||
|
||||
MasterResource = Literal['strain', 'customer', 'site', 'line', 'warehouse']
|
||||
|
||||
@@ -71,17 +74,35 @@ class MasterService:
|
||||
"""菌种、客户、现场、产线和仓库主数据服务层。"""
|
||||
|
||||
@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':
|
||||
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:
|
||||
raise ServiceException(message='目标客户不存在')
|
||||
assert_record_in_scope(customer, scope, '目标客户')
|
||||
if customer.status != '0':
|
||||
raise ServiceException(message='目标客户已停用')
|
||||
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:
|
||||
raise ServiceException(message='目标客户现场不存在')
|
||||
assert_record_in_scope(site, scope, '目标客户现场')
|
||||
if site.status != '0':
|
||||
raise ServiceException(message='目标客户现场已停用')
|
||||
|
||||
@@ -127,50 +148,37 @@ class MasterService:
|
||||
code = getattr(page_object, config['code_field'])
|
||||
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
|
||||
async def _list(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
resource: MasterResource,
|
||||
query_object: BaseModel,
|
||||
scope: TestBatchScope,
|
||||
is_page: bool,
|
||||
) -> 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]
|
||||
records = result.rows if isinstance(result, PageModel) else result
|
||||
models = [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
|
||||
]
|
||||
rows = [config['vo'].model_validate(record) for record in records]
|
||||
if isinstance(result, PageModel):
|
||||
return result.model_copy(update={'rows': rows})
|
||||
return rows
|
||||
|
||||
@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]
|
||||
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:
|
||||
raise ServiceException(message=f'{config["label"]}不存在')
|
||||
model = config['vo'].model_validate(record)
|
||||
removability = await MasterDao.get_master_removability(db, resource, [resource_id])
|
||||
return model.model_copy(update={'can_remove': removability.get(resource_id, False)})
|
||||
assert_record_in_scope(record, scope, config['label'])
|
||||
return config['vo'].model_validate(record)
|
||||
|
||||
@classmethod
|
||||
async def _add(
|
||||
@@ -178,13 +186,16 @@ class MasterService:
|
||||
db: AsyncSession,
|
||||
resource: MasterResource,
|
||||
page_object: BaseModel,
|
||||
current_user: CurrentUserModel,
|
||||
scope: TestBatchScope,
|
||||
) -> CrudResponseModel:
|
||||
config = _RESOURCE_CONFIG[resource]
|
||||
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)
|
||||
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()
|
||||
return CrudResponseModel(is_success=True, message='新增成功')
|
||||
except IntegrityError:
|
||||
@@ -200,18 +211,27 @@ class MasterService:
|
||||
db: AsyncSession,
|
||||
resource: MasterResource,
|
||||
page_object: BaseModel,
|
||||
current_user: CurrentUserModel,
|
||||
scope: TestBatchScope,
|
||||
) -> CrudResponseModel:
|
||||
config = _RESOURCE_CONFIG[resource]
|
||||
try:
|
||||
await cls._prepare_test_write(db, current_user, scope)
|
||||
resource_id = getattr(page_object, config['id_field'])
|
||||
if resource_id is None:
|
||||
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:
|
||||
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 getattr(MasterDao, f'update_{resource}')(db, page_object)
|
||||
await getattr(MasterDao, f'update_{resource}')(db, page_object, scope)
|
||||
await db.commit()
|
||||
return CrudResponseModel(is_success=True, message='更新成功')
|
||||
except IntegrityError:
|
||||
@@ -221,173 +241,216 @@ class MasterService:
|
||||
await db.rollback()
|
||||
raise
|
||||
|
||||
@classmethod
|
||||
async def _remove(
|
||||
cls,
|
||||
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
|
||||
@staticmethod
|
||||
async def reject_physical_delete() -> NoReturn:
|
||||
raise ServiceWarning('', '不支持单条物理删除,请通过测试数据批次清理')
|
||||
|
||||
@classmethod
|
||||
async def list_strain(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
query_object: StrainPageQueryModel,
|
||||
scope: TestBatchScope,
|
||||
is_page: bool = True,
|
||||
) -> 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
|
||||
async def detail_strain(cls, db: AsyncSession, strain_id: int) -> StrainModel:
|
||||
return await cls._detail(db, 'strain', strain_id)
|
||||
async def detail_strain(cls, db: AsyncSession, strain_id: int, scope: TestBatchScope) -> StrainModel:
|
||||
return await cls._detail(db, 'strain', strain_id, scope)
|
||||
|
||||
@classmethod
|
||||
async def add_strain(cls, db: AsyncSession, strain: StrainModel) -> CrudResponseModel:
|
||||
return await cls._add(db, 'strain', strain)
|
||||
async def add_strain(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
strain: StrainModel,
|
||||
current_user: CurrentUserModel,
|
||||
scope: TestBatchScope,
|
||||
) -> CrudResponseModel:
|
||||
return await cls._add(db, 'strain', strain, current_user, scope)
|
||||
|
||||
@classmethod
|
||||
async def edit_strain(cls, db: AsyncSession, strain: StrainModel) -> CrudResponseModel:
|
||||
return await cls._edit(db, 'strain', strain)
|
||||
async def edit_strain(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
strain: StrainModel,
|
||||
current_user: CurrentUserModel,
|
||||
scope: TestBatchScope,
|
||||
) -> CrudResponseModel:
|
||||
return await cls._edit(db, 'strain', strain, current_user, scope)
|
||||
|
||||
@classmethod
|
||||
async def remove_strain(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
value: int | DeleteStrainModel,
|
||||
) -> CrudResponseModel:
|
||||
return await cls._remove(db, 'strain', value)
|
||||
) -> NoReturn:
|
||||
return await cls.reject_physical_delete()
|
||||
|
||||
@classmethod
|
||||
async def list_customer(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
query_object: CustomerPageQueryModel,
|
||||
scope: TestBatchScope,
|
||||
is_page: bool = True,
|
||||
) -> 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
|
||||
async def detail_customer(cls, db: AsyncSession, customer_id: int) -> CustomerModel:
|
||||
return await cls._detail(db, 'customer', customer_id)
|
||||
async def detail_customer(cls, db: AsyncSession, customer_id: int, scope: TestBatchScope) -> CustomerModel:
|
||||
return await cls._detail(db, 'customer', customer_id, scope)
|
||||
|
||||
@classmethod
|
||||
async def add_customer(cls, db: AsyncSession, customer: CustomerModel) -> CrudResponseModel:
|
||||
return await cls._add(db, 'customer', customer)
|
||||
async def add_customer(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
customer: CustomerModel,
|
||||
current_user: CurrentUserModel,
|
||||
scope: TestBatchScope,
|
||||
) -> CrudResponseModel:
|
||||
return await cls._add(db, 'customer', customer, current_user, scope)
|
||||
|
||||
@classmethod
|
||||
async def edit_customer(cls, db: AsyncSession, customer: CustomerModel) -> CrudResponseModel:
|
||||
return await cls._edit(db, 'customer', customer)
|
||||
async def edit_customer(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
customer: CustomerModel,
|
||||
current_user: CurrentUserModel,
|
||||
scope: TestBatchScope,
|
||||
) -> CrudResponseModel:
|
||||
return await cls._edit(db, 'customer', customer, current_user, scope)
|
||||
|
||||
@classmethod
|
||||
async def remove_customer(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
value: int | DeleteCustomerModel,
|
||||
) -> CrudResponseModel:
|
||||
return await cls._remove(db, 'customer', value)
|
||||
) -> NoReturn:
|
||||
return await cls.reject_physical_delete()
|
||||
|
||||
@classmethod
|
||||
async def list_site(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
query_object: SitePageQueryModel,
|
||||
scope: TestBatchScope,
|
||||
is_page: bool = True,
|
||||
) -> 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
|
||||
async def detail_site(cls, db: AsyncSession, site_id: int) -> SiteModel:
|
||||
return await cls._detail(db, 'site', site_id)
|
||||
async def detail_site(cls, db: AsyncSession, site_id: int, scope: TestBatchScope) -> SiteModel:
|
||||
return await cls._detail(db, 'site', site_id, scope)
|
||||
|
||||
@classmethod
|
||||
async def add_site(cls, db: AsyncSession, site: SiteModel) -> CrudResponseModel:
|
||||
return await cls._add(db, 'site', site)
|
||||
async def add_site(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
site: SiteModel,
|
||||
current_user: CurrentUserModel,
|
||||
scope: TestBatchScope,
|
||||
) -> CrudResponseModel:
|
||||
return await cls._add(db, 'site', site, current_user, scope)
|
||||
|
||||
@classmethod
|
||||
async def edit_site(cls, db: AsyncSession, site: SiteModel) -> CrudResponseModel:
|
||||
return await cls._edit(db, 'site', site)
|
||||
async def edit_site(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
site: SiteModel,
|
||||
current_user: CurrentUserModel,
|
||||
scope: TestBatchScope,
|
||||
) -> CrudResponseModel:
|
||||
return await cls._edit(db, 'site', site, current_user, scope)
|
||||
|
||||
@classmethod
|
||||
async def remove_site(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
value: int | DeleteSiteModel,
|
||||
) -> CrudResponseModel:
|
||||
return await cls._remove(db, 'site', value)
|
||||
) -> NoReturn:
|
||||
return await cls.reject_physical_delete()
|
||||
|
||||
@classmethod
|
||||
async def list_line(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
query_object: LinePageQueryModel,
|
||||
scope: TestBatchScope,
|
||||
is_page: bool = True,
|
||||
) -> 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
|
||||
async def detail_line(cls, db: AsyncSession, line_id: int) -> LineModel:
|
||||
return await cls._detail(db, 'line', line_id)
|
||||
async def detail_line(cls, db: AsyncSession, line_id: int, scope: TestBatchScope) -> LineModel:
|
||||
return await cls._detail(db, 'line', line_id, scope)
|
||||
|
||||
@classmethod
|
||||
async def add_line(cls, db: AsyncSession, line: LineModel) -> CrudResponseModel:
|
||||
return await cls._add(db, 'line', line)
|
||||
async def add_line(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
line: LineModel,
|
||||
current_user: CurrentUserModel,
|
||||
scope: TestBatchScope,
|
||||
) -> CrudResponseModel:
|
||||
return await cls._add(db, 'line', line, current_user, scope)
|
||||
|
||||
@classmethod
|
||||
async def edit_line(cls, db: AsyncSession, line: LineModel) -> CrudResponseModel:
|
||||
return await cls._edit(db, 'line', line)
|
||||
async def edit_line(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
line: LineModel,
|
||||
current_user: CurrentUserModel,
|
||||
scope: TestBatchScope,
|
||||
) -> CrudResponseModel:
|
||||
return await cls._edit(db, 'line', line, current_user, scope)
|
||||
|
||||
@classmethod
|
||||
async def remove_line(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
value: int | DeleteLineModel,
|
||||
) -> CrudResponseModel:
|
||||
return await cls._remove(db, 'line', value)
|
||||
) -> NoReturn:
|
||||
return await cls.reject_physical_delete()
|
||||
|
||||
@classmethod
|
||||
async def list_warehouse(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
query_object: WarehousePageQueryModel,
|
||||
scope: TestBatchScope,
|
||||
is_page: bool = True,
|
||||
) -> 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
|
||||
async def detail_warehouse(cls, db: AsyncSession, warehouse_id: int) -> WarehouseModel:
|
||||
return await cls._detail(db, 'warehouse', warehouse_id)
|
||||
async def detail_warehouse(cls, db: AsyncSession, warehouse_id: int, scope: TestBatchScope) -> WarehouseModel:
|
||||
return await cls._detail(db, 'warehouse', warehouse_id, scope)
|
||||
|
||||
@classmethod
|
||||
async def add_warehouse(cls, db: AsyncSession, warehouse: WarehouseModel) -> CrudResponseModel:
|
||||
return await cls._add(db, 'warehouse', warehouse)
|
||||
async def add_warehouse(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
warehouse: WarehouseModel,
|
||||
current_user: CurrentUserModel,
|
||||
scope: TestBatchScope,
|
||||
) -> CrudResponseModel:
|
||||
return await cls._add(db, 'warehouse', warehouse, current_user, scope)
|
||||
|
||||
@classmethod
|
||||
async def edit_warehouse(cls, db: AsyncSession, warehouse: WarehouseModel) -> CrudResponseModel:
|
||||
return await cls._edit(db, 'warehouse', warehouse)
|
||||
async def edit_warehouse(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
warehouse: WarehouseModel,
|
||||
current_user: CurrentUserModel,
|
||||
scope: TestBatchScope,
|
||||
) -> CrudResponseModel:
|
||||
return await cls._edit(db, 'warehouse', warehouse, current_user, scope)
|
||||
|
||||
@classmethod
|
||||
async def remove_warehouse(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
value: int | DeleteWarehouseModel,
|
||||
) -> CrudResponseModel:
|
||||
return await cls._remove(db, 'warehouse', value)
|
||||
) -> NoReturn:
|
||||
return await cls.reject_physical_delete()
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import secrets
|
||||
from datetime import datetime
|
||||
from io import BytesIO
|
||||
from typing import Literal
|
||||
from typing import Literal, NoReturn
|
||||
|
||||
import qrcode
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
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.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.operation_do import BizPalletInventory
|
||||
from module_business.entity.vo.master_vo import (
|
||||
@@ -22,6 +24,7 @@ from module_business.entity.vo.master_vo import (
|
||||
PalletPageQueryModel,
|
||||
UserAssignModel,
|
||||
)
|
||||
from module_business.service.test_batch_access_service import TestBatchAccessService
|
||||
|
||||
AssignmentResource = Literal['warehouse', 'site']
|
||||
|
||||
@@ -41,33 +44,43 @@ class PalletService:
|
||||
qrcode.make(qr_token).save(buffer, format='PNG')
|
||||
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
|
||||
async def get_pallet_qrcode(cls, db: AsyncSession, pallet_id: int) -> bytes:
|
||||
pallet = await MasterDao.get_pallet(db, pallet_id)
|
||||
async def get_pallet_qrcode(cls, db: AsyncSession, pallet_id: int, scope: TestBatchScope) -> bytes:
|
||||
pallet = await MasterDao.get_pallet(db, pallet_id, scope)
|
||||
if pallet is None:
|
||||
raise ServiceException(message='托盘不存在')
|
||||
assert_record_in_scope(pallet, scope, '托盘')
|
||||
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
|
||||
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 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='托盘库存不存在')
|
||||
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:
|
||||
raise ServiceException(message='托盘不存在')
|
||||
assert_record_in_scope(pallet, scope, '托盘')
|
||||
return pallet, inventory
|
||||
|
||||
@classmethod
|
||||
@@ -75,42 +88,41 @@ class PalletService:
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
query_object: PalletPageQueryModel,
|
||||
scope: TestBatchScope,
|
||||
is_page: bool = True,
|
||||
) -> 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
|
||||
models = [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]
|
||||
rows = [PalletModel.model_validate(record) for record in records]
|
||||
if isinstance(result, PageModel):
|
||||
return result.model_copy(update={'rows': rows})
|
||||
return rows
|
||||
|
||||
@classmethod
|
||||
async def detail_pallet(cls, db: AsyncSession, pallet_id: int) -> PalletModel:
|
||||
pallet = await MasterDao.get_pallet(db, pallet_id)
|
||||
async def detail_pallet(cls, db: AsyncSession, pallet_id: int, scope: TestBatchScope) -> PalletModel:
|
||||
pallet = await MasterDao.get_pallet(db, pallet_id, scope)
|
||||
if pallet is None:
|
||||
raise ServiceException(message='托盘不存在')
|
||||
model = PalletModel.model_validate(pallet)
|
||||
removability = await MasterDao.get_pallet_removability(db, [pallet_id])
|
||||
return model.model_copy(update={'can_remove': removability.get(pallet_id, False)})
|
||||
assert_record_in_scope(pallet, scope, '托盘')
|
||||
return PalletModel.model_validate(pallet)
|
||||
|
||||
@classmethod
|
||||
async def add_pallet(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
pallet: PalletCreateModel,
|
||||
current_user_name: str,
|
||||
current_user: CurrentUserModel,
|
||||
scope: TestBatchScope,
|
||||
) -> CrudResponseModel:
|
||||
try:
|
||||
await cls._prepare_test_write(db, current_user, scope)
|
||||
if await MasterDao.find_pallet_by_code(db, pallet.pallet_code) is not None:
|
||||
raise ServiceException(message='托盘编号已存在')
|
||||
|
||||
current_time = datetime.now()
|
||||
current_user_name = current_user.user.user_name
|
||||
pallet_record = BizPallet(
|
||||
test_batch_id=scope.batch_id,
|
||||
pallet_code=pallet.pallet_code,
|
||||
qr_token=secrets.token_urlsafe(32),
|
||||
capacity_packages=pallet.capacity_packages,
|
||||
@@ -124,6 +136,7 @@ class PalletService:
|
||||
pallet_record = await MasterDao.add_pallet(db, pallet_record)
|
||||
inventory = BizPalletInventory(
|
||||
pallet_id=pallet_record.pallet_id,
|
||||
test_batch_id=scope.batch_id,
|
||||
flow_state='UNSTOCKED',
|
||||
remaining_packages=0,
|
||||
version=0,
|
||||
@@ -144,16 +157,19 @@ class PalletService:
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
pallet: PalletEditModel,
|
||||
current_user_name: str,
|
||||
current_user: CurrentUserModel,
|
||||
scope: TestBatchScope,
|
||||
) -> CrudResponseModel:
|
||||
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:
|
||||
raise ServiceException(message='托盘容量不能小于当前剩余包数')
|
||||
await MasterDao.update_pallet(
|
||||
db,
|
||||
pallet,
|
||||
update_by=current_user_name,
|
||||
scope,
|
||||
update_by=current_user.user.user_name,
|
||||
update_time=datetime.now(),
|
||||
)
|
||||
await db.commit()
|
||||
@@ -170,27 +186,8 @@ class PalletService:
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
value: int | DeletePalletModel,
|
||||
) -> CrudResponseModel:
|
||||
try:
|
||||
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
|
||||
) -> NoReturn:
|
||||
raise ServiceWarning('', '不支持单条物理删除,请通过测试数据批次清理')
|
||||
|
||||
@classmethod
|
||||
async def _list_resource_users(
|
||||
@@ -198,12 +195,17 @@ class PalletService:
|
||||
db: AsyncSession,
|
||||
resource: AssignmentResource,
|
||||
resource_id: int,
|
||||
scope: TestBatchScope,
|
||||
) -> 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]}不存在')
|
||||
assert_record_in_scope(record, scope, _ASSIGNMENT_LABELS[resource])
|
||||
|
||||
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 [
|
||||
BusinessUserOptionModel(
|
||||
user_id=user.user_id,
|
||||
@@ -219,16 +221,18 @@ class PalletService:
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
warehouse_id: int,
|
||||
scope: TestBatchScope,
|
||||
) -> list[BusinessUserOptionModel]:
|
||||
return await cls._list_resource_users(db, 'warehouse', warehouse_id)
|
||||
return await cls._list_resource_users(db, 'warehouse', warehouse_id, scope)
|
||||
|
||||
@classmethod
|
||||
async def list_site_users(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
site_id: int,
|
||||
scope: TestBatchScope,
|
||||
) -> list[BusinessUserOptionModel]:
|
||||
return await cls._list_resource_users(db, 'site', site_id)
|
||||
return await cls._list_resource_users(db, 'site', site_id, scope)
|
||||
|
||||
@classmethod
|
||||
async def _assign_resource_users(
|
||||
@@ -237,18 +241,21 @@ class PalletService:
|
||||
resource: AssignmentResource,
|
||||
resource_id: int,
|
||||
assignment: UserAssignModel,
|
||||
current_user: CurrentUserModel,
|
||||
scope: TestBatchScope,
|
||||
) -> CrudResponseModel:
|
||||
label = _ASSIGNMENT_LABELS[resource]
|
||||
try:
|
||||
if (
|
||||
await getattr(MasterDao, f'get_{resource}')(
|
||||
db,
|
||||
resource_id,
|
||||
for_update=True,
|
||||
)
|
||||
is None
|
||||
):
|
||||
await cls._prepare_test_write(db, current_user, scope)
|
||||
record = await getattr(MasterDao, f'get_{resource}')(
|
||||
db,
|
||||
resource_id,
|
||||
scope,
|
||||
for_update=True,
|
||||
)
|
||||
if record is None:
|
||||
raise ServiceException(message=f'{label}不存在')
|
||||
assert_record_in_scope(record, scope, label)
|
||||
users = await MasterDao.list_assignable_users_by_ids(db, assignment.user_ids)
|
||||
if {user.user_id for user in users} != set(assignment.user_ids):
|
||||
raise ServiceException(message='存在不存在、停用或已删除的用户')
|
||||
@@ -268,8 +275,17 @@ class PalletService:
|
||||
db: AsyncSession,
|
||||
warehouse_id: int,
|
||||
assignment: UserAssignModel,
|
||||
current_user: CurrentUserModel,
|
||||
scope: TestBatchScope,
|
||||
) -> 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
|
||||
async def assign_site_users(
|
||||
@@ -277,5 +293,14 @@ class PalletService:
|
||||
db: AsyncSession,
|
||||
site_id: int,
|
||||
assignment: UserAssignModel,
|
||||
current_user: CurrentUserModel,
|
||||
scope: TestBatchScope,
|
||||
) -> 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_time',
|
||||
'remark',
|
||||
'can_remove',
|
||||
},
|
||||
StrainPageQueryModel: {'page_num', 'page_size', 'strain_code', 'strain_name', 'status'},
|
||||
DeleteStrainModel: {'strain_ids'},
|
||||
@@ -111,7 +110,6 @@ MASTER_MODEL_FIELDS = {
|
||||
'update_by',
|
||||
'update_time',
|
||||
'remark',
|
||||
'can_remove',
|
||||
},
|
||||
CustomerPageQueryModel: {'page_num', 'page_size', 'customer_code', 'customer_name', 'status'},
|
||||
DeleteCustomerModel: {'customer_ids'},
|
||||
@@ -127,7 +125,6 @@ MASTER_MODEL_FIELDS = {
|
||||
'update_by',
|
||||
'update_time',
|
||||
'remark',
|
||||
'can_remove',
|
||||
},
|
||||
SitePageQueryModel: {'page_num', 'page_size', 'customer_id', 'site_code', 'site_name', 'status'},
|
||||
DeleteSiteModel: {'site_ids'},
|
||||
@@ -142,7 +139,6 @@ MASTER_MODEL_FIELDS = {
|
||||
'update_by',
|
||||
'update_time',
|
||||
'remark',
|
||||
'can_remove',
|
||||
},
|
||||
LinePageQueryModel: {'page_num', 'page_size', 'site_id', 'line_code', 'line_name', 'status'},
|
||||
DeleteLineModel: {'line_ids'},
|
||||
@@ -157,7 +153,6 @@ MASTER_MODEL_FIELDS = {
|
||||
'update_by',
|
||||
'update_time',
|
||||
'remark',
|
||||
'can_remove',
|
||||
},
|
||||
WarehousePageQueryModel: {'page_num', 'page_size', 'warehouse_code', 'warehouse_name', 'status'},
|
||||
DeleteWarehouseModel: {'warehouse_ids'},
|
||||
@@ -172,7 +167,6 @@ MASTER_MODEL_FIELDS = {
|
||||
'create_time',
|
||||
'update_by',
|
||||
'update_time',
|
||||
'can_remove',
|
||||
},
|
||||
PalletCreateModel: {'pallet_code', 'capacity_packages', 'remark'},
|
||||
PalletEditModel: {'pallet_id', 'capacity_packages', 'asset_status', 'remark'},
|
||||
@@ -858,6 +852,26 @@ class BusinessVoContractTest(unittest.TestCase):
|
||||
with self.subTest(model=model_class.__name__):
|
||||
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:
|
||||
for model_class in TEST_BATCH_MODEL_FIELDS:
|
||||
with self.subTest(model=model_class.__name__):
|
||||
|
||||
Reference in New Issue
Block a user