924 lines
42 KiB
Python
924 lines
42 KiB
Python
"""市场移动端运费批次:作用域、脱敏与原子写规则。"""
|
|
|
|
from datetime import datetime, timedelta
|
|
from decimal import Decimal
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from fastapi import HTTPException
|
|
from pydantic import ValidationError
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.dialects import mysql
|
|
from sqlalchemy.orm import sessionmaker
|
|
|
|
from app.models.base import Base
|
|
from app.models.models import (
|
|
BizCellSampleType, BizCollectionProtocol, BizCollectionRegionStaff, BizProtocolCollectionInfo,
|
|
BizProtocolPartyCustomer, BizProtocolSample, BizSampleFreight,
|
|
BizSampleFreightItem, SysDictData, SysDictType, SysPost, SysPostRole,
|
|
SysDept, SysRole, SysRoleDataScopeDetail, SysUser, SysUserPost, SysUserRole,
|
|
)
|
|
from app.models.research_donation import (
|
|
BizResearchDonation, BizResearchDonationCollection,
|
|
BizResearchDonationParty, BizResearchDonationSample,
|
|
)
|
|
from app.services.market_field_scope import MarketFieldContext
|
|
|
|
TABLES = [
|
|
BizCollectionProtocol.__table__, BizProtocolCollectionInfo.__table__,
|
|
BizProtocolPartyCustomer.__table__, BizProtocolSample.__table__,
|
|
BizResearchDonation.__table__, BizResearchDonationCollection.__table__,
|
|
BizResearchDonationParty.__table__, BizResearchDonationSample.__table__,
|
|
BizSampleFreight.__table__, BizSampleFreightItem.__table__,
|
|
BizCellSampleType.__table__,
|
|
BizCollectionRegionStaff.__table__, SysDictType.__table__,
|
|
SysDictData.__table__, SysUser.__table__, SysRole.__table__,
|
|
SysRoleDataScopeDetail.__table__, SysUserRole.__table__, SysPost.__table__,
|
|
SysPostRole.__table__, SysUserPost.__table__, SysDept.__table__,
|
|
]
|
|
|
|
|
|
def _session():
|
|
engine = create_engine('sqlite:///:memory:')
|
|
Base.metadata.create_all(engine, tables=TABLES)
|
|
db = sessionmaker(bind=engine)()
|
|
now = datetime(2026, 10, 11, 8, 0)
|
|
db.add_all([
|
|
SysDictType(id='dt', dict_name='运输方式', dict_type='sample_transport_mode', tenant_no='tenant-a', create_dept_id='', create_time=now, update_time=now, del_flag=0),
|
|
SysDictData(id='dd', dict_label='顺丰', dict_value='sf', dict_type='sample_transport_mode', tenant_no='tenant-a', create_dept_id='', create_time=now, update_time=now, del_flag=0),
|
|
SysUser(id='u1', dept_id='', user_no='u1', nick_name='专员', password='', tenant_no='tenant-a', create_dept_id='', create_time=now, update_time=now, del_flag=0),
|
|
SysUser(id='m1', dept_id='', user_no='m1', nick_name='经理', password='', tenant_no='tenant-a', create_dept_id='', create_time=now, update_time=now, del_flag=0),
|
|
SysRole(id='fixture-super-role', role_name='超级管理员', data_scope='1', status='1', tenant_no='tenant-a', create_dept_id='', create_time=now, update_time=now, del_flag=0),
|
|
SysUserRole(id='fixture-super-u1', user_id='u1', role_id='fixture-super-role', tenant_no='tenant-a', create_dept_id='', create_time=now, update_time=now, del_flag=0),
|
|
SysUserRole(id='fixture-super-m1', user_id='m1', role_id='fixture-super-role', tenant_no='tenant-a', create_dept_id='', create_time=now, update_time=now, del_flag=0),
|
|
])
|
|
db.commit()
|
|
return db
|
|
|
|
|
|
def _ctx(kind='specialist', user_id='u1'):
|
|
return MarketFieldContext('tenant-a', user_id, kind)
|
|
|
|
|
|
def _user(user_id='u1'):
|
|
return {'user_id': user_id, 'tenant_no': 'tenant-a', 'role': 'super_admin'}
|
|
|
|
|
|
def _sample(db, key, *, source='cell_storage', sales='u1', manager='m1', region='r1', tenant='tenant-a', deleted=0, created=None):
|
|
created = created or datetime(2026, 10, 10, 8, 0)
|
|
if source == 'cell_storage':
|
|
parent = BizCollectionProtocol(id=f'cp-{key}', protocol_no=f'CP-{key}', tenant_no=tenant, create_dept_id='', create_time=created, del_flag=deleted)
|
|
collection = BizProtocolCollectionInfo(id=f'cc-{key}', collection_protocol_id=parent.id, region_id=region, sales_user_id=sales, region_manager_user_id=manager, tenant_no=tenant, create_dept_id='', create_time=created, del_flag=deleted)
|
|
party = BizProtocolPartyCustomer(id=f'pc-{key}', collection_protocol_id=parent.id, mother_name=f'客户-{key}', tenant_no=tenant, create_dept_id='', create_time=created, del_flag=deleted)
|
|
sample = BizProtocolSample(id=f'cs-{key}', collection_protocol_id=parent.id, sample_code=f'C-{key}', tenant_no=tenant, create_dept_id='', create_time=created, del_flag=deleted)
|
|
else:
|
|
parent = BizResearchDonation(id=f'rd-{key}', donation_no=f'RD-{key}', tenant_no=tenant, create_dept_id='', create_time=created, del_flag=deleted)
|
|
collection = BizResearchDonationCollection(id=f'rc-{key}', research_donation_id=parent.id, region_id=region, sales_user_id=sales, region_manager_user_id=manager, tenant_no=tenant, create_dept_id='', create_time=created, del_flag=deleted)
|
|
party = BizResearchDonationParty(id=f'rp-{key}', research_donation_id=parent.id, mother_name=f'客户-{key}', tenant_no=tenant, create_dept_id='', create_time=created, del_flag=deleted)
|
|
sample = BizResearchDonationSample(id=f'rs-{key}', research_donation_id=parent.id, sample_code=f'R-{key}', tenant_no=tenant, create_dept_id='', create_time=created, del_flag=deleted)
|
|
db.add_all([parent, collection, party, sample])
|
|
db.commit()
|
|
return sample, parent
|
|
|
|
|
|
def _freight(db, key, samples, *, tenant='tenant-a', amount='12.30', deleted=0, create_by='u1'):
|
|
now = datetime(2026, 10, 11, 8, 0)
|
|
freight = BizSampleFreight(id=f'f-{key}', freight_no=f'YF-{key}', transport_mode='sf', freight_amount=Decimal(amount), tenant_no=tenant, create_by=create_by, create_dept_id='', create_time=now, update_time=now, del_flag=deleted)
|
|
db.add(freight)
|
|
for pos, (source, sample, parent) in enumerate(samples):
|
|
db.add(BizSampleFreightItem(id=f'fi-{key}-{pos}', freight_id=freight.id, sample_source=source, sample_id=sample.id, sample_code=sample.sample_code, protocol_id=parent.id, tenant_no=tenant, create_dept_id='', create_time=now, update_time=now, del_flag=0))
|
|
db.commit()
|
|
return freight
|
|
|
|
|
|
def _items(*pairs):
|
|
return [{'sampleSource': source, 'sampleId': sample.id} for source, sample in pairs]
|
|
|
|
|
|
def test_specialist_sees_shared_batch_but_only_own_items_and_count():
|
|
from app.services.market_freight import get_freight_detail, list_freights
|
|
db = _session()
|
|
own, own_parent = _sample(db, 'own')
|
|
other, other_parent = _sample(db, 'other', sales='u2')
|
|
freight = _freight(db, 'mixed', [('cell_storage', own, own_parent), ('cell_storage', other, other_parent)])
|
|
rows, total = list_freights(db, ctx=_ctx(), user_info=_user(), include_items=True)
|
|
detail = get_freight_detail(db, freight.id, ctx=_ctx(), user_info=_user())
|
|
assert total == 1 and rows[0]['visibleItemCount'] == 1
|
|
assert [item['sampleCode'] for item in rows[0]['items']] == ['C-own']
|
|
assert [item['sampleCode'] for item in detail['items']] == ['C-own']
|
|
assert rows[0]['createBy'] == detail['createBy'] == 'u1'
|
|
assert rows[0]['createByName'] == detail['createByName'] == '专员'
|
|
assert 'linkedSampleCount' not in rows[0] and 'linkedSampleCount' not in detail
|
|
assert 'hasHiddenItems' not in detail
|
|
|
|
|
|
def test_freight_creator_name_uses_historical_nickname_without_id_fallback():
|
|
from app.services.market_freight import get_freight_detail, list_freights
|
|
|
|
db = _session()
|
|
now = datetime(2026, 10, 11, 8, 0)
|
|
db.add_all(
|
|
[
|
|
SysUser(id='deleted-creator', dept_id='', user_no='deleted-no', nick_name='历史创建人', password='', tenant_no='tenant-a', create_dept_id='', create_time=now, update_time=now, del_flag=1),
|
|
SysUser(id='blank-creator', dept_id='', user_no='raw-creator-no', nick_name='', password='', tenant_no='tenant-a', create_dept_id='', create_time=now, update_time=now, del_flag=1),
|
|
]
|
|
)
|
|
first_sample, first_parent = _sample(db, 'creator-history')
|
|
second_sample, second_parent = _sample(db, 'creator-blank')
|
|
historical = _freight(
|
|
db,
|
|
'creator-history',
|
|
[('cell_storage', first_sample, first_parent)],
|
|
create_by='deleted-creator',
|
|
)
|
|
blank = _freight(
|
|
db,
|
|
'creator-blank',
|
|
[('cell_storage', second_sample, second_parent)],
|
|
create_by='blank-creator',
|
|
)
|
|
|
|
items, total = list_freights(db, ctx=_ctx(), user_info=_user())
|
|
by_id = {item['id']: item for item in items}
|
|
historical_detail = get_freight_detail(
|
|
db, historical.id, ctx=_ctx(), user_info=_user()
|
|
)
|
|
blank_detail = get_freight_detail(
|
|
db, blank.id, ctx=_ctx(), user_info=_user()
|
|
)
|
|
|
|
assert total == 2
|
|
assert by_id[historical.id]['createByName'] == '历史创建人'
|
|
assert historical_detail['createByName'] == '历史创建人'
|
|
assert by_id[blank.id]['createByName'] == ''
|
|
assert blank_detail['createByName'] == ''
|
|
|
|
|
|
def test_batch_without_visible_item_is_not_listed_or_addressable():
|
|
from app.services.market_freight import get_freight_detail, list_freights
|
|
db = _session()
|
|
other, parent = _sample(db, 'other', sales='u2')
|
|
freight = _freight(db, 'hidden', [('cell_storage', other, parent)])
|
|
assert list_freights(db, ctx=_ctx(), user_info=_user())[1] == 0
|
|
with pytest.raises(HTTPException) as exc:
|
|
get_freight_detail(db, freight.id, ctx=_ctx(), user_info=_user())
|
|
assert exc.value.status_code == 404
|
|
|
|
|
|
def test_keyword_and_source_filters_use_only_visible_items():
|
|
from app.services.market_freight import list_freights
|
|
db = _session()
|
|
own, op = _sample(db, 'own')
|
|
secret, sp = _sample(db, 'SECRET', source='research_donation', sales='u2')
|
|
_freight(db, 'mixed', [('cell_storage', own, op), ('research_donation', secret, sp)])
|
|
assert list_freights(db, ctx=_ctx(), user_info=_user(), keyword='SECRET')[1] == 0
|
|
assert list_freights(db, ctx=_ctx(), user_info=_user(), sample_source='research_donation')[1] == 0
|
|
assert list_freights(db, ctx=_ctx(), user_info=_user(), sample_source='cell_storage')[1] == 1
|
|
|
|
|
|
def test_freight_list_filters_create_time_range():
|
|
from app.services.market_freight import list_freights
|
|
|
|
db = _session()
|
|
first, first_parent = _sample(db, 'created-in-range')
|
|
second, second_parent = _sample(db, 'created-outside')
|
|
in_range = _freight(
|
|
db,
|
|
'created-in-range',
|
|
[('cell_storage', first, first_parent)],
|
|
)
|
|
outside = _freight(
|
|
db,
|
|
'created-outside',
|
|
[('cell_storage', second, second_parent)],
|
|
)
|
|
in_range.create_time = datetime(2026, 10, 11, 8, 0)
|
|
outside.create_time = datetime(2026, 9, 1, 8, 0)
|
|
db.commit()
|
|
|
|
items, total = list_freights(
|
|
db,
|
|
ctx=_ctx(),
|
|
user_info=_user(),
|
|
create_time_start='2026-10-01',
|
|
create_time_end='2026-10-13',
|
|
)
|
|
|
|
assert total == 1
|
|
assert [item['id'] for item in items] == [in_range.id]
|
|
|
|
|
|
def test_manager_scope_is_region_union_document_manager_for_both_sources():
|
|
from app.services.market_freight import get_freight_detail
|
|
db = _session()
|
|
db.add(BizCollectionRegionStaff(id='staff', region_id='managed', user_id='m1', is_leader=1, tenant_no='tenant-a', create_dept_id='', del_flag=0))
|
|
db.commit()
|
|
region_sample, rp = _sample(db, 'region', sales='u9', manager='x', region='managed')
|
|
assigned, ap = _sample(db, 'assigned', source='research_donation', sales='u9', manager='m1', region='other')
|
|
hidden, hp = _sample(db, 'hidden', sales='m1', manager='x', region='other')
|
|
freight = _freight(db, 'manager', [('cell_storage', region_sample, rp), ('research_donation', assigned, ap), ('cell_storage', hidden, hp)])
|
|
detail = get_freight_detail(db, freight.id, ctx=_ctx('manager', 'm1'), user_info=_user('m1'))
|
|
assert {item['sampleCode'] for item in detail['items']} == {'C-region', 'R-assigned'}
|
|
assert detail['visibleItemCount'] == 2
|
|
assert all('sampleTypeName' in item and 'motherName' in item and 'newbornName' in item for item in detail['items'])
|
|
|
|
|
|
def test_dual_role_uses_fixed_route_context_not_union_between_pages():
|
|
from app.services.market_freight import list_freights
|
|
db = _session()
|
|
specialist, sp = _sample(db, 'specialist', sales='m1', manager='x', region='other')
|
|
manager, mp = _sample(db, 'manager', sales='u9', manager='m1', region='other')
|
|
_freight(db, 'specialist', [('cell_storage', specialist, sp)])
|
|
_freight(db, 'manager', [('cell_storage', manager, mp)])
|
|
specialist_rows, _ = list_freights(db, ctx=_ctx('specialist', 'm1'), user_info=_user('m1'))
|
|
manager_rows, _ = list_freights(db, ctx=_ctx('manager', 'm1'), user_info=_user('m1'))
|
|
assert [row['freightNo'] for row in specialist_rows] == ['YF-specialist']
|
|
assert [row['freightNo'] for row in manager_rows] == ['YF-manager']
|
|
|
|
|
|
def test_cross_tenant_deleted_parent_and_deleted_sample_are_invisible():
|
|
from app.services.market_freight import list_freights
|
|
db = _session()
|
|
cross, cp = _sample(db, 'cross', tenant='tenant-b')
|
|
deleted, dp = _sample(db, 'deleted', deleted=1)
|
|
_freight(db, 'cross', [('cell_storage', cross, cp)], tenant='tenant-b')
|
|
_freight(db, 'deleted', [('cell_storage', deleted, dp)])
|
|
assert list_freights(db, ctx=_ctx(), user_info=_user())[1] == 0
|
|
|
|
|
|
def test_sample_options_are_scoped_sql_paginated_stable_and_derive_link_state():
|
|
from app.services.market_freight import list_sample_options
|
|
db = _session()
|
|
base = datetime(2026, 10, 10, 8, 0)
|
|
first, fp = _sample(db, 'first', created=base)
|
|
_sample(db, 'second', source='research_donation', created=base + timedelta(hours=1))
|
|
_sample(db, 'third', created=base + timedelta(hours=2))
|
|
_sample(db, 'hidden', sales='u2', created=base + timedelta(hours=3))
|
|
_freight(db, 'linked', [('cell_storage', first, fp)])
|
|
page1, total = list_sample_options(db, ctx=_ctx(), user_info=_user(), page=1, page_size=2)
|
|
page2, total2 = list_sample_options(db, ctx=_ctx(), user_info=_user(), page=2, page_size=2)
|
|
assert total == total2 == 3
|
|
assert [x['sampleCode'] for x in page1] == ['C-third', 'R-second']
|
|
assert [x['sampleCode'] for x in page2] == ['C-first']
|
|
assert page2[0]['freightId'] == 'f-linked'
|
|
assert page2[0]['freightNo'] == 'YF-linked'
|
|
assert set(('protocolNo', 'customerName', 'newbornName', 'sampleTypeName')).issubset(page2[0])
|
|
|
|
|
|
def test_create_derives_snapshots_and_decimal_string_and_requires_visible_item():
|
|
from app.services.market_freight import create_freight
|
|
db = _session()
|
|
own, parent = _sample(db, 'own')
|
|
result = create_freight(db, ctx=_ctx(), user_info=_user(), transport_mode='sf', freight_amount=Decimal('12.30'), items=_items(('cell_storage', own)))
|
|
item = db.query(BizSampleFreightItem).filter_by(freight_id=result['id']).one()
|
|
assert result['freightAmount'] == '12.30'
|
|
assert item.sample_code == 'C-own' and item.protocol_id == parent.id
|
|
with pytest.raises(ValueError, match='至少'):
|
|
create_freight(db, ctx=_ctx(), user_info=_user(), transport_mode='sf', freight_amount='1.00', items=[])
|
|
|
|
|
|
def test_create_prevalidates_every_item_and_rolls_back_without_partial_batch():
|
|
from app.services.market_freight import create_freight
|
|
db = _session()
|
|
own, _ = _sample(db, 'own')
|
|
other, _ = _sample(db, 'other', sales='u2')
|
|
before = db.query(BizSampleFreight).count()
|
|
with pytest.raises(HTTPException):
|
|
create_freight(db, ctx=_ctx(), user_info=_user(), transport_mode='sf', freight_amount='1.00', items=_items(('cell_storage', own), ('cell_storage', other)))
|
|
assert db.query(BizSampleFreight).count() == before
|
|
|
|
|
|
def test_link_rejects_hidden_and_existing_other_batch_without_silent_move():
|
|
from app.services.market_freight import link_samples
|
|
db = _session()
|
|
own, op = _sample(db, 'own')
|
|
other, _ = _sample(db, 'other', sales='u2')
|
|
linked, lp = _sample(db, 'linked')
|
|
target = _freight(db, 'target', [('cell_storage', own, op)])
|
|
source = _freight(db, 'source', [('cell_storage', linked, lp)])
|
|
for sample in (other, linked):
|
|
with pytest.raises((HTTPException, ValueError)):
|
|
link_samples(db, target.id, ctx=_ctx(), user_info=_user(), items=_items(('cell_storage', sample)))
|
|
assert db.query(BizSampleFreightItem).filter_by(sample_id=linked.id, del_flag=0).one().freight_id == source.id
|
|
|
|
|
|
def test_unlink_prevalidates_all_items_and_rejects_hidden_or_nonexistent():
|
|
from app.services.market_freight import unlink_samples
|
|
db = _session()
|
|
own, op = _sample(db, 'own')
|
|
other, hp = _sample(db, 'hidden', sales='u2')
|
|
freight = _freight(db, 'mixed', [('cell_storage', own, op), ('cell_storage', other, hp)])
|
|
missing, _ = _sample(db, 'missing')
|
|
for sample in (other, missing):
|
|
with pytest.raises((HTTPException, ValueError)):
|
|
unlink_samples(db, freight.id, ctx=_ctx(), user_info=_user(), items=_items(('cell_storage', sample)))
|
|
assert db.query(BizSampleFreightItem).filter_by(freight_id=freight.id, del_flag=0).count() == 2
|
|
|
|
|
|
def test_unlink_last_visible_item_disappears_without_revealing_hidden_remaining():
|
|
from app.services.market_freight import list_freights, unlink_samples
|
|
db = _session()
|
|
own, op = _sample(db, 'own')
|
|
other, hp = _sample(db, 'hidden', sales='u2')
|
|
freight = _freight(db, 'mixed', [('cell_storage', own, op), ('cell_storage', other, hp)])
|
|
result = unlink_samples(db, freight.id, ctx=_ctx(), user_info=_user(), items=_items(('cell_storage', own)))
|
|
assert result == {'id': freight.id, 'visibleItemCount': 0, 'disappeared': True}
|
|
assert list_freights(db, ctx=_ctx(), user_info=_user())[1] == 0
|
|
assert db.query(BizSampleFreight).filter_by(id=freight.id).one().del_flag == 0
|
|
|
|
|
|
@pytest.mark.parametrize('kind,user_id', [('specialist', 'u1'), ('manager', 'm1')])
|
|
def test_shared_outside_scope_header_is_immutable_for_both_roles(kind, user_id):
|
|
from app.services.market_freight import update_freight
|
|
db = _session()
|
|
visible, vp = _sample(db, 'visible', sales=user_id if kind == 'specialist' else 'u9', manager=user_id if kind == 'manager' else 'x')
|
|
hidden, hp = _sample(db, 'hidden', sales='other', manager='other')
|
|
freight = _freight(db, 'mixed', [('cell_storage', visible, vp), ('cell_storage', hidden, hp)])
|
|
with pytest.raises(PermissionError, match='全部'):
|
|
update_freight(db, freight.id, ctx=_ctx(kind, user_id), user_info=_user(user_id), freight_amount='99.00')
|
|
assert db.query(BizSampleFreight).filter_by(id=freight.id).one().freight_amount == Decimal('12.30')
|
|
|
|
|
|
def test_header_update_allowed_when_all_active_items_are_visible():
|
|
from app.services.market_freight import update_freight
|
|
db = _session()
|
|
own, op = _sample(db, 'own')
|
|
freight = _freight(db, 'own', [('cell_storage', own, op)])
|
|
result = update_freight(db, freight.id, ctx=_ctx(), user_info=_user(), freight_amount='99.10')
|
|
assert result['freightAmount'] == '99.10'
|
|
|
|
|
|
@pytest.mark.parametrize('kind,user_id', [('specialist', 'u1'), ('manager', 'm1')])
|
|
@pytest.mark.parametrize('mixed', [False, True])
|
|
def test_detail_edit_action_requires_every_active_item_visible_without_leaking_totals(
|
|
kind, user_id, mixed
|
|
):
|
|
from app.services.market_freight import get_freight_detail
|
|
from app.services.market_support import decorate_allowed_actions
|
|
|
|
db = _session()
|
|
visible, visible_parent = _sample(
|
|
db,
|
|
f'action-visible-{kind}-{mixed}',
|
|
sales=user_id if kind == 'specialist' else 'other-sales',
|
|
manager=user_id if kind == 'manager' else 'other-manager',
|
|
)
|
|
rows = [('cell_storage', visible, visible_parent)]
|
|
if mixed:
|
|
hidden, hidden_parent = _sample(
|
|
db,
|
|
f'action-hidden-{kind}',
|
|
sales='hidden-sales',
|
|
manager='hidden-manager',
|
|
)
|
|
rows.append(('cell_storage', hidden, hidden_parent))
|
|
freight = _freight(db, f'action-{kind}-{mixed}', rows)
|
|
|
|
detail = get_freight_detail(
|
|
db,
|
|
freight.id,
|
|
ctx=_ctx(kind, user_id),
|
|
user_info=_user(user_id),
|
|
)
|
|
rendered = decorate_allowed_actions(
|
|
'freight', detail, scope_kind=kind, detail=True
|
|
)
|
|
|
|
expected = ['link', 'unlink'] if mixed else ['edit', 'link', 'unlink']
|
|
assert detail['allowedActions'] == expected
|
|
assert rendered['allowedActions'] == expected
|
|
for hidden_key in (
|
|
'allItemsVisible',
|
|
'hiddenItemCount',
|
|
'totalItemCount',
|
|
'activeItemCount',
|
|
):
|
|
assert hidden_key not in detail
|
|
|
|
|
|
def test_dto_forbids_scope_snapshots_status_and_non_decimal_amounts():
|
|
from app.schemas.market_mobile import MarketFreightCreateBody, MarketFreightItemsBody
|
|
with pytest.raises(ValidationError):
|
|
MarketFreightItemsBody.model_validate({'items': [{'sampleSource': 'cell_storage', 'sampleId': 's1', 'sampleCode': 'forged'}]})
|
|
for extra in ('tenantNo', 'scope', 'status'):
|
|
with pytest.raises(ValidationError):
|
|
MarketFreightCreateBody.model_validate({'transportMode': 'sf', 'freightAmount': '1.00', 'items': [{'sampleSource': 'cell_storage', 'sampleId': 's1'}], extra: 'x'})
|
|
with pytest.raises(ValidationError):
|
|
MarketFreightCreateBody.model_validate({'transportMode': 'sf', 'freightAmount': 'NaN', 'items': [{'sampleSource': 'cell_storage', 'sampleId': 's1'}]})
|
|
|
|
|
|
def test_fixed_routes_and_precise_permissions_are_registered():
|
|
from app.api.market_mobile import router
|
|
paths = [(route.path, route.methods) for route in router.routes]
|
|
for kind in ('specialist', 'manager'):
|
|
prefix = f'/mini/market/{kind}/freights'
|
|
assert any(path == prefix and 'GET' in methods for path, methods in paths)
|
|
assert any(path == prefix and 'POST' in methods for path, methods in paths)
|
|
assert any(path == f'{prefix}/sample-options' and 'GET' in methods for path, methods in paths)
|
|
source = (Path(__file__).parents[1] / 'app/api/market_mobile.py').read_text()
|
|
assert "f'mini:market:{scope_kind}:freight'" in source
|
|
|
|
|
|
def test_migration_has_mysql57_indexes_unique_active_link_and_duplicate_preflight():
|
|
sql = (Path(__file__).parents[2] / 'docs/sql/migrations/2026-10-11-market-freight-indexes.sql').read_text()
|
|
lowered = sql.lower()
|
|
assert 'information_schema.statistics' in lowered
|
|
assert 'generated always as' in lowered
|
|
assert 'unique' in lowered and 'active' in lowered
|
|
assert 'group by tenant_no, sample_source, sample_id' in lowered
|
|
assert 'having count(*) > 1' in lowered
|
|
assert 'create procedure' in lowered and 'signal sqlstate' in lowered
|
|
assert 'character_maximum_length' in lowered
|
|
assert 'data_type' in lowered and 'is_nullable' in lowered
|
|
assert 'normalized_generation_expression' in lowered
|
|
assert "uk_sample_freight_tenant_no" in lowered
|
|
assert 'exact_freight_unique_indexes' in lowered
|
|
|
|
|
|
def test_write_service_contains_locking_postlock_validation_and_integrity_retry():
|
|
source = (Path(__file__).parents[1] / 'app/services/market_freight.py').read_text()
|
|
assert source.count('with_for_update') >= 3
|
|
assert 'IntegrityError' in source
|
|
assert '_lock_batch_header' in source
|
|
assert '_lock_sample_graph' in source
|
|
assert '_lock_active_items' in source
|
|
|
|
|
|
def test_multiple_party_rows_do_not_duplicate_visible_item_or_count():
|
|
from app.services.market_freight import get_freight_detail
|
|
db = _session()
|
|
sample, parent = _sample(db, 'one')
|
|
db.add(BizProtocolPartyCustomer(id='pc-one-duplicate', collection_protocol_id=parent.id, mother_name='重复客户行', tenant_no='tenant-a', create_dept_id='', del_flag=0))
|
|
db.commit()
|
|
freight = _freight(db, 'one', [('cell_storage', sample, parent)])
|
|
detail = get_freight_detail(db, freight.id, ctx=_ctx(), user_info=_user())
|
|
assert detail['visibleItemCount'] == 1
|
|
assert len(detail['items']) == 1
|
|
|
|
|
|
def test_deleted_batch_is_invisible_even_with_visible_active_item():
|
|
from app.services.market_freight import list_freights
|
|
db = _session()
|
|
sample, parent = _sample(db, 'one')
|
|
_freight(db, 'deleted-batch', [('cell_storage', sample, parent)], deleted=1)
|
|
assert list_freights(db, ctx=_ctx(), user_info=_user())[1] == 0
|
|
|
|
|
|
def test_context_user_mismatch_fails_closed():
|
|
from app.services.market_freight import list_sample_options
|
|
db = _session()
|
|
_sample(db, 'one')
|
|
with pytest.raises(PermissionError):
|
|
list_sample_options(db, ctx=_ctx(), user_info=_user('u2'))
|
|
|
|
|
|
def test_manager_can_link_sample_in_managed_region():
|
|
from app.services.market_freight import link_samples
|
|
db = _session()
|
|
db.add(BizCollectionRegionStaff(id='staff-link', region_id='managed', user_id='m1', is_leader=1, tenant_no='tenant-a', create_dept_id='', del_flag=0))
|
|
db.commit()
|
|
existing, ep = _sample(db, 'existing', sales='u9', manager='m1')
|
|
new_sample, _ = _sample(db, 'new', sales='u9', manager='x', region='managed')
|
|
freight = _freight(db, 'manager-link', [('cell_storage', existing, ep)])
|
|
detail = link_samples(db, freight.id, ctx=_ctx('manager', 'm1'), user_info=_user('m1'), items=_items(('cell_storage', new_sample)))
|
|
assert {item['sampleCode'] for item in detail['items']} == {'C-existing', 'C-new'}
|
|
|
|
|
|
def test_batch_paging_is_stable_and_total_counts_only_visible_batches():
|
|
from app.services.market_freight import list_freights
|
|
db = _session()
|
|
first, fp = _sample(db, 'first')
|
|
second, sp = _sample(db, 'second')
|
|
hidden, hp = _sample(db, 'hidden-page', sales='u2')
|
|
f1 = _freight(db, 'first', [('cell_storage', first, fp)])
|
|
f2 = _freight(db, 'second', [('cell_storage', second, sp)])
|
|
_freight(db, 'hidden-page', [('cell_storage', hidden, hp)])
|
|
f1.create_time = f2.create_time
|
|
db.commit()
|
|
page1, total1 = list_freights(db, ctx=_ctx(), user_info=_user(), page=1, page_size=1)
|
|
page2, total2 = list_freights(db, ctx=_ctx(), user_info=_user(), page=2, page_size=1)
|
|
assert total1 == total2 == 2
|
|
assert [page1[0]['id'], page2[0]['id']] == ['f-second', 'f-first']
|
|
|
|
|
|
@pytest.mark.parametrize('source', ['cell_storage', 'research_donation'])
|
|
def test_sample_options_choose_latest_party_once_before_count_and_paging(source):
|
|
from app.services.market_freight import list_sample_options
|
|
db = _session()
|
|
base = datetime(2026, 10, 10, 8, 0)
|
|
target, parent = _sample(db, f'duplicate-{source}', source=source, created=base)
|
|
_sample(db, f'other-{source}', source=source, created=base + timedelta(hours=1))
|
|
if source == 'cell_storage':
|
|
db.add(BizProtocolPartyCustomer(
|
|
id='zz-latest-cell-party', collection_protocol_id=parent.id,
|
|
mother_name='最新细胞客户', tenant_no='tenant-a', create_dept_id='',
|
|
create_time=base + timedelta(hours=2), del_flag=0,
|
|
))
|
|
latest_name = '最新细胞客户'
|
|
else:
|
|
db.add(BizResearchDonationParty(
|
|
id='zz-latest-donation-party', research_donation_id=parent.id,
|
|
mother_name='最新科研客户', tenant_no='tenant-a', create_dept_id='',
|
|
create_time=base + timedelta(hours=2), del_flag=0,
|
|
))
|
|
latest_name = '最新科研客户'
|
|
db.commit()
|
|
|
|
page1, total1 = list_sample_options(
|
|
db, ctx=_ctx(), user_info=_user(), page=1, page_size=1,
|
|
sample_source=source,
|
|
)
|
|
page2, total2 = list_sample_options(
|
|
db, ctx=_ctx(), user_info=_user(), page=2, page_size=1,
|
|
sample_source=source,
|
|
)
|
|
rows = page1 + page2
|
|
|
|
assert total1 == total2 == 2
|
|
assert sum(row['sampleId'] == target.id for row in rows) == 1
|
|
target_row = next(row for row in rows if row['sampleId'] == target.id)
|
|
assert target_row['customerName'] == latest_name
|
|
|
|
|
|
def test_mysql_lock_queries_are_current_reads_with_stable_global_order():
|
|
from app.services.market_freight import (
|
|
_active_item_lock_query,
|
|
_collection_lock_query,
|
|
_leader_lock_query,
|
|
_sample_parent_lock_query,
|
|
)
|
|
db = _session()
|
|
ctx = _ctx()
|
|
sample_sql = str(
|
|
_sample_parent_lock_query(
|
|
db, 'cell_storage', ['z', 'a'], ctx
|
|
).statement.compile(dialect=mysql.dialect())
|
|
).lower()
|
|
collection_sql = str(
|
|
_collection_lock_query(
|
|
db, 'cell_storage', ['p-z', 'p-a'], ctx
|
|
).statement.compile(dialect=mysql.dialect())
|
|
).lower()
|
|
item_sql = str(
|
|
_active_item_lock_query(
|
|
db, ctx, freight_id='f1', keys=[('cell_storage', 'z')]
|
|
).statement.compile(dialect=mysql.dialect())
|
|
).lower()
|
|
leader_sql = str(
|
|
_leader_lock_query(db, _ctx('manager', 'm1')).statement.compile(
|
|
dialect=mysql.dialect()
|
|
)
|
|
).lower()
|
|
|
|
for sql in (sample_sql, collection_sql, item_sql, leader_sql):
|
|
assert ' for update' in sql
|
|
assert ' order by ' in sql
|
|
assert 'tenant_no' in sql and 'del_flag' in sql
|
|
assert 'sales_user_id' in collection_sql
|
|
assert 'biz_protocol_collection_info' in collection_sql
|
|
assert 'biz_collection_region_staff' in leader_sql
|
|
|
|
|
|
def test_all_write_paths_use_batch_graph_item_lock_order():
|
|
source = (Path(__file__).parents[1] / 'app/services/market_freight.py').read_text()
|
|
for start_name, end_name in (
|
|
('def update_freight', 'def link_samples'),
|
|
('def link_samples', 'def unlink_samples'),
|
|
('def unlink_samples', None),
|
|
):
|
|
block = source[source.index(start_name):]
|
|
if end_name:
|
|
block = block[:block.index(end_name)]
|
|
assert block.index('_lock_batch_header') < block.index('_lock_sample_graph')
|
|
assert block.index('_lock_sample_graph') < block.index('_lock_active_items')
|
|
create_block = source[source.index('def create_freight'):source.index('def update_freight')]
|
|
assert create_block.index('_lock_sample_graph') < create_block.index('_lock_active_items')
|
|
|
|
|
|
def test_header_update_rejects_assignment_changed_after_prior_visible_read():
|
|
from app.services.market_freight import get_freight_detail, update_freight
|
|
db = _session()
|
|
sample, parent = _sample(db, 'scope-race')
|
|
freight = _freight(db, 'scope-race', [('cell_storage', sample, parent)])
|
|
assert get_freight_detail(db, freight.id, ctx=_ctx(), user_info=_user())['visibleItemCount'] == 1
|
|
|
|
collection = db.query(BizProtocolCollectionInfo).filter_by(
|
|
collection_protocol_id=parent.id
|
|
).one()
|
|
db.query(BizProtocolCollectionInfo).filter_by(id=collection.id).update(
|
|
{'sales_user_id': 'u2'}, synchronize_session=False
|
|
)
|
|
assert collection.sales_user_id == 'u1'
|
|
|
|
with pytest.raises(HTTPException) as exc:
|
|
update_freight(
|
|
db, freight.id, ctx=_ctx(), user_info=_user(), freight_amount='55.00'
|
|
)
|
|
assert exc.value.status_code == 404
|
|
assert db.query(BizSampleFreight).filter_by(id=freight.id).one().freight_amount == Decimal('12.30')
|
|
|
|
|
|
def test_final_item_lock_rejects_participant_snapshot_change():
|
|
from app.services.market_freight import _lock_active_items
|
|
db = _session()
|
|
first, fp = _sample(db, 'snapshot-first')
|
|
second, sp = _sample(db, 'snapshot-second')
|
|
freight = _freight(db, 'snapshot', [('cell_storage', first, fp)])
|
|
expected = [('cell_storage', first.id)]
|
|
db.add(BizSampleFreightItem(
|
|
id='fi-snapshot-race', freight_id=freight.id,
|
|
sample_source='cell_storage', sample_id=second.id,
|
|
sample_code=second.sample_code, protocol_id=sp.id,
|
|
tenant_no='tenant-a', create_dept_id='', del_flag=0,
|
|
))
|
|
db.commit()
|
|
|
|
with pytest.raises(PermissionError, match='发生变化'):
|
|
_lock_active_items(
|
|
db, _ctx(), freight_id=freight.id,
|
|
expected_batch_keys=expected,
|
|
)
|
|
|
|
|
|
def test_lock_graph_does_not_rely_on_exists_scope_for_current_field_values():
|
|
source = (Path(__file__).parents[1] / 'app/services/market_freight.py').read_text()
|
|
block = source[source.index('def _lock_sample_graph'):source.index('def _lock_active_items')]
|
|
assert 'apply_cell_protocol_scope' not in block
|
|
assert 'apply_donation_scope' not in block
|
|
assert '_current_field_visible_parent_ids' in block
|
|
assert '_current_data_scope_parent_ids' in block
|
|
|
|
|
|
def test_manager_header_update_rejects_current_leader_revocation():
|
|
from app.services.market_freight import get_freight_detail, update_freight
|
|
db = _session()
|
|
leader = BizCollectionRegionStaff(
|
|
id='leader-race', region_id='managed-race', user_id='m1', is_leader=1,
|
|
tenant_no='tenant-a', create_dept_id='', del_flag=0,
|
|
)
|
|
db.add(leader)
|
|
db.commit()
|
|
sample, parent = _sample(
|
|
db, 'leader-race', sales='u9', manager='other', region='managed-race'
|
|
)
|
|
freight = _freight(db, 'leader-race', [('cell_storage', sample, parent)])
|
|
ctx = _ctx('manager', 'm1')
|
|
assert get_freight_detail(db, freight.id, ctx=ctx, user_info=_user('m1'))['visibleItemCount'] == 1
|
|
db.query(BizCollectionRegionStaff).filter_by(id=leader.id).update(
|
|
{'is_leader': 0}, synchronize_session=False
|
|
)
|
|
assert leader.is_leader == 1
|
|
|
|
with pytest.raises(HTTPException) as exc:
|
|
update_freight(
|
|
db, freight.id, ctx=ctx, user_info=_user('m1'), freight_amount='88.00'
|
|
)
|
|
assert exc.value.status_code == 404
|
|
|
|
|
|
def test_header_update_uses_current_role_data_scope_not_prior_snapshot():
|
|
from app.services.market_freight import get_freight_detail, update_freight
|
|
db = _session()
|
|
now = datetime(2026, 10, 11, 8, 0)
|
|
db.add_all([
|
|
SysUser(
|
|
id='scope-user', dept_id='dept-current', user_no='scope-user',
|
|
nick_name='范围用户', password='', tenant_no='tenant-a',
|
|
create_dept_id='', create_time=now, update_time=now, del_flag=0,
|
|
),
|
|
SysRole(
|
|
id='scope-role', role_name='范围角色', data_scope='1', status='1',
|
|
tenant_no='tenant-a', create_dept_id='', create_time=now,
|
|
update_time=now, del_flag=0,
|
|
),
|
|
SysUserRole(
|
|
id='scope-user-role', user_id='scope-user', role_id='scope-role',
|
|
tenant_no='tenant-a', create_dept_id='', create_time=now,
|
|
update_time=now, del_flag=0,
|
|
),
|
|
])
|
|
db.commit()
|
|
sample, parent = _sample(db, 'data-scope', sales='scope-user')
|
|
parent.create_by = 'another-user'
|
|
parent.create_dept_id = 'another-dept'
|
|
db.commit()
|
|
freight = _freight(db, 'data-scope', [('cell_storage', sample, parent)])
|
|
ctx = _ctx('specialist', 'scope-user')
|
|
user = {'user_id': 'scope-user', 'tenant_no': 'tenant-a', 'role': 'admin'}
|
|
assert get_freight_detail(db, freight.id, ctx=ctx, user_info=user)['visibleItemCount'] == 1
|
|
cached_role = db.query(SysRole).filter_by(id='scope-role').one()
|
|
db.query(SysRole).filter_by(id='scope-role').update(
|
|
{'data_scope': '4'}, synchronize_session=False
|
|
)
|
|
assert cached_role.data_scope == '1'
|
|
|
|
with pytest.raises(HTTPException) as exc:
|
|
update_freight(
|
|
db, freight.id, ctx=ctx, user_info=user, freight_amount='77.00'
|
|
)
|
|
assert exc.value.status_code == 404
|
|
|
|
|
|
def test_current_data_scope_preserves_post_roles_custom_scope_and_union():
|
|
from app.services.market_freight import _current_data_scope_parent_ids
|
|
db = _session()
|
|
now = datetime(2026, 10, 11, 8, 0)
|
|
db.add_all([
|
|
SysUser(
|
|
id='union-user', dept_id='union-home', user_no='union-user',
|
|
nick_name='并集用户', password='', tenant_no='tenant-a',
|
|
create_dept_id='', create_time=now, update_time=now, del_flag=0,
|
|
),
|
|
SysRole(
|
|
id='custom-role', role_name='自定义范围', data_scope='5', status='1',
|
|
tenant_no='tenant-a', create_dept_id='', create_time=now,
|
|
update_time=now, del_flag=0,
|
|
),
|
|
SysRole(
|
|
id='self-role', role_name='本人范围', data_scope='4', status='1',
|
|
tenant_no='tenant-a', create_dept_id='', create_time=now,
|
|
update_time=now, del_flag=0,
|
|
),
|
|
SysUserRole(
|
|
id='union-direct', user_id='union-user', role_id='custom-role',
|
|
tenant_no='tenant-a', create_dept_id='', create_time=now,
|
|
update_time=now, del_flag=0,
|
|
),
|
|
SysRoleDataScopeDetail(
|
|
id='union-custom-detail', role_id='custom-role', scope_type='dept',
|
|
scope_value='custom-dept', tenant_no='tenant-a', create_dept_id='',
|
|
create_time=now, update_time=now, del_flag=0,
|
|
),
|
|
SysPost(
|
|
id='union-post', post_name='继承职位', post_code='union-post',
|
|
status='1', tenant_no='tenant-a', create_dept_id='',
|
|
create_time=now, update_time=now, del_flag=0,
|
|
),
|
|
SysUserPost(
|
|
id='union-user-post', user_id='union-user', post_id='union-post',
|
|
tenant_no='tenant-a', create_dept_id='', create_time=now,
|
|
update_time=now, del_flag=0,
|
|
),
|
|
SysPostRole(
|
|
id='union-post-role', post_id='union-post', role_id='self-role',
|
|
tenant_no='tenant-a', create_dept_id='', create_time=now,
|
|
update_time=now, del_flag=0,
|
|
),
|
|
])
|
|
db.commit()
|
|
_, custom_parent = _sample(db, 'union-custom')
|
|
_, self_parent = _sample(db, 'union-self')
|
|
_, denied_parent = _sample(db, 'union-denied')
|
|
custom_parent.create_dept_id = 'custom-dept'
|
|
self_parent.create_by = 'union-user'
|
|
denied_parent.create_dept_id = 'other-dept'
|
|
denied_parent.create_by = 'other-user'
|
|
db.commit()
|
|
|
|
visible = _current_data_scope_parent_ids(
|
|
db,
|
|
[custom_parent, self_parent, denied_parent],
|
|
_ctx('specialist', 'union-user'),
|
|
{'user_id': 'union-user', 'tenant_no': 'tenant-a', 'role': 'admin'},
|
|
)
|
|
|
|
assert visible == {custom_parent.id, self_parent.id}
|
|
|
|
|
|
def test_current_data_scope_preserves_department_descendant_scope():
|
|
from app.services.market_freight import _current_data_scope_parent_ids
|
|
db = _session()
|
|
now = datetime(2026, 10, 11, 8, 0)
|
|
db.add_all([
|
|
SysUser(
|
|
id='dept-user', dept_id='dept-root', user_no='dept-user',
|
|
nick_name='部门用户', password='', tenant_no='tenant-a',
|
|
create_dept_id='', create_time=now, update_time=now, del_flag=0,
|
|
),
|
|
SysRole(
|
|
id='dept-role', role_name='部门以下', data_scope='3', status='1',
|
|
tenant_no='tenant-a', create_dept_id='', create_time=now,
|
|
update_time=now, del_flag=0,
|
|
),
|
|
SysUserRole(
|
|
id='dept-user-role', user_id='dept-user', role_id='dept-role',
|
|
tenant_no='tenant-a', create_dept_id='', create_time=now,
|
|
update_time=now, del_flag=0,
|
|
),
|
|
SysDept(
|
|
id='dept-root', parent_id='0', dept_name='根部门', status='1',
|
|
tenant_no='tenant-a', create_dept_id='', create_time=now,
|
|
update_time=now, del_flag=0,
|
|
),
|
|
SysDept(
|
|
id='dept-child', parent_id='dept-root', dept_name='子部门', status='1',
|
|
tenant_no='tenant-a', create_dept_id='', create_time=now,
|
|
update_time=now, del_flag=0,
|
|
),
|
|
])
|
|
db.commit()
|
|
_, child_parent = _sample(db, 'dept-child')
|
|
_, denied_parent = _sample(db, 'dept-denied')
|
|
child_parent.create_dept_id = 'dept-child'
|
|
denied_parent.create_dept_id = 'other-dept'
|
|
db.commit()
|
|
|
|
visible = _current_data_scope_parent_ids(
|
|
db,
|
|
[child_parent, denied_parent],
|
|
_ctx('specialist', 'dept-user'),
|
|
{'user_id': 'dept-user', 'tenant_no': 'tenant-a', 'role': 'admin'},
|
|
)
|
|
|
|
assert visible == {child_parent.id}
|
|
|
|
|
|
def test_locked_data_scope_ignores_payload_superadmin_across_tenants():
|
|
from app.services.market_freight import (
|
|
_current_data_scope_parent_ids,
|
|
get_freight_detail,
|
|
)
|
|
db = _session()
|
|
now = datetime(2026, 10, 11, 8, 0)
|
|
db.add_all([
|
|
SysUser(
|
|
id='cross-user', dept_id='cross-dept', user_no='cross-user',
|
|
nick_name='跨租户用户', password='', tenant_no='tenant-a',
|
|
create_dept_id='', create_time=now, update_time=now, del_flag=0,
|
|
),
|
|
SysRole(
|
|
id='default-super-role', role_name='超级管理员', data_scope='1',
|
|
status='1', tenant_no='zkxw', create_dept_id='', create_time=now,
|
|
update_time=now, del_flag=0,
|
|
),
|
|
SysUserRole(
|
|
id='default-super-link', user_id='cross-user',
|
|
role_id='default-super-role', tenant_no='zkxw', create_dept_id='',
|
|
create_time=now, update_time=now, del_flag=0,
|
|
),
|
|
SysRole(
|
|
id='business-self-role', role_name='业务专员', data_scope='4',
|
|
status='1', tenant_no='tenant-a', create_dept_id='', create_time=now,
|
|
update_time=now, del_flag=0,
|
|
),
|
|
SysUserRole(
|
|
id='business-self-link', user_id='cross-user',
|
|
role_id='business-self-role', tenant_no='tenant-a',
|
|
create_dept_id='', create_time=now, update_time=now, del_flag=0,
|
|
),
|
|
])
|
|
db.commit()
|
|
sample, parent = _sample(
|
|
db, 'cross-tenant-scope', sales='cross-user'
|
|
)
|
|
parent.create_by = 'another-user'
|
|
parent.create_dept_id = 'another-dept'
|
|
db.commit()
|
|
ctx = _ctx('specialist', 'cross-user')
|
|
forged_default_tenant_payload = {
|
|
'user_id': 'cross-user', 'tenant_no': 'tenant-a', 'role': 'super_admin'
|
|
}
|
|
freight = _freight(
|
|
db, 'cross-tenant-scope', [('cell_storage', sample, parent)]
|
|
)
|
|
|
|
assert _current_data_scope_parent_ids(
|
|
db, [parent], ctx, forged_default_tenant_payload
|
|
) == set()
|
|
with pytest.raises(HTTPException) as exc:
|
|
get_freight_detail(
|
|
db, freight.id, ctx=ctx, user_info=forged_default_tenant_payload
|
|
)
|
|
assert exc.value.status_code == 404
|
|
|
|
db.add_all([
|
|
SysRole(
|
|
id='business-super-role', role_name='超级管理员', data_scope='4',
|
|
status='1', tenant_no='tenant-a', create_dept_id='', create_time=now,
|
|
update_time=now, del_flag=0,
|
|
),
|
|
SysPost(
|
|
id='business-super-post', post_name='业务租户超级管理职位',
|
|
post_code='business-super-post', status='1', tenant_no='tenant-a',
|
|
create_dept_id='', create_time=now, update_time=now, del_flag=0,
|
|
),
|
|
SysUserPost(
|
|
id='business-super-user-post', user_id='cross-user',
|
|
post_id='business-super-post', tenant_no='tenant-a',
|
|
create_dept_id='', create_time=now, update_time=now, del_flag=0,
|
|
),
|
|
SysPostRole(
|
|
id='business-super-post-role', post_id='business-super-post',
|
|
role_id='business-super-role', tenant_no='tenant-a',
|
|
create_dept_id='', create_time=now, update_time=now, del_flag=0,
|
|
),
|
|
])
|
|
db.commit()
|
|
|
|
assert _current_data_scope_parent_ids(
|
|
db, [parent], ctx, forged_default_tenant_payload
|
|
) == {parent.id}
|
|
assert get_freight_detail(
|
|
db, freight.id, ctx=ctx, user_info=forged_default_tenant_payload
|
|
)['visibleItemCount'] == 1
|