diff --git a/backend/app/services/market_protocol.py b/backend/app/services/market_protocol.py index 1174f8d8..c6eea5fa 100644 --- a/backend/app/services/market_protocol.py +++ b/backend/app/services/market_protocol.py @@ -984,11 +984,14 @@ def list_protocols( for row in cell_rows ) if source in (None, SOURCE_DONATION): - if status or collected_from or collected_until: - donation_enabled = False - else: - donation_enabled = True - if not donation_enabled: + # 混合列表(source=None)下状态/采集时间只覆盖细胞存储,捐赠侧跳过。 + # 科研捐赠单源列表支持 protocolStatus→donation_status;采集时间筛仍不支持捐赠。 + skip_donation = False + if source is None and (status or collected_from or collected_until): + skip_donation = True + elif source == SOURCE_DONATION and (collected_from or collected_until): + skip_donation = True + if skip_donation: query = None else: query = apply_donation_scope( @@ -1001,6 +1004,8 @@ def list_protocols( if query is None: donation_rows = [] else: + if status and source == SOURCE_DONATION: + query = query.filter(BizResearchDonation.donation_status == status) if created_from: query = query.filter(BizResearchDonation.create_time >= created_from) if created_until: diff --git a/backend/tests/test_market_protocol_api.py b/backend/tests/test_market_protocol_api.py index 2e1bd3a2..9cdda2f4 100644 --- a/backend/tests/test_market_protocol_api.py +++ b/backend/tests/test_market_protocol_api.py @@ -3733,6 +3733,29 @@ def test_protocol_list_filters_status_create_time_and_sample_collect_time(): assert [item['id'] for item in items] == ['hit'] +def test_donation_list_filters_by_protocol_status_alias(): + from app.services.market_protocol import list_protocols + + db = _session() + _masters(db) + hit = _donation(db, 'don-hit', created=datetime(2026, 10, 8, 9, 0)) + hit.donation_status = 'sxz' + miss = _donation(db, 'don-miss', created=datetime(2026, 10, 8, 9, 0)) + miss.donation_status = 'dsx' + db.commit() + + items, total = list_protocols( + db, + ctx=_ctx('specialist'), + user_info=_user(), + source='research_donation', + protocol_status='sxz', + ) + + assert total == 1 + assert [item['id'] for item in items] == ['don-hit'] + + def test_cell_create_rolls_back_main_customer_and_children_on_child_failure(): from app.services.market_protocol import create_protocol diff --git a/mobile-oa-h5/src/market/list-filters.js b/mobile-oa-h5/src/market/list-filters.js index a8959e95..f767c287 100644 --- a/mobile-oa-h5/src/market/list-filters.js +++ b/mobile-oa-h5/src/market/list-filters.js @@ -40,7 +40,8 @@ export function createListFilters(module, source = '') { result.source = SOURCES.has(text(source)) ? text(source) : 'cell_storage' return { source: result.source, - protocolStatus: result.protocolStatus + protocolStatus: result.protocolStatus, + projectId: '' } } return result diff --git a/mobile-oa-h5/src/market/list-filters.test.js b/mobile-oa-h5/src/market/list-filters.test.js index f4040940..57b675d1 100644 --- a/mobile-oa-h5/src/market/list-filters.test.js +++ b/mobile-oa-h5/src/market/list-filters.test.js @@ -15,11 +15,13 @@ test('creates isolated defaults for every market module', () => { assert.deepEqual(first, { source: 'cell_storage', - protocolStatus: 'sxz' + protocolStatus: 'sxz', + projectId: '' }) assert.deepEqual(second, { source: 'research_donation', - protocolStatus: '' + protocolStatus: '', + projectId: '' }) assert.deepEqual(createListFilters('presign'), { orderStatus: '' }) assert.deepEqual(createListFilters('payment-chase'), { chaseType: '', payMode: '' }) @@ -66,8 +68,14 @@ test('drops incomplete ranges and counts only user-selectable filters', () => { }), { progressStatus: 'yjq' }) assert.equal(activeFilterCount('protocol', { source: 'cell_storage', - protocolStatus: 'sxz' + protocolStatus: 'sxz', + projectId: '' }), 1) + assert.equal(activeFilterCount('protocol', { + source: 'research_donation', + protocolStatus: 'sxz', + projectId: 'p1' + }), 2) assert.equal(activeFilterCount('freight', { transportMode: '', sampleSource: 'research_donation' }), 1) diff --git a/mobile-oa-h5/src/market/mobile-parity-ui.test.js b/mobile-oa-h5/src/market/mobile-parity-ui.test.js index 18da6eae..7806c1a9 100644 --- a/mobile-oa-h5/src/market/mobile-parity-ui.test.js +++ b/mobile-oa-h5/src/market/mobile-parity-ui.test.js @@ -313,6 +313,7 @@ test('protocol list uses inline protocol-status dropdown like presign instead of assert.match(protocol, /inlineDropdownFilters/) assert.match(protocol, /全部协议状态/) + assert.match(protocol, /source:\s*'research_donation'/) assert.match(protocol, /filterFields:\s*\[\]/) assert.doesNotMatch(protocol, /createTimeRange/) assert.doesNotMatch(protocol, /sampleCollectTimeRange/) @@ -320,6 +321,9 @@ test('protocol list uses inline protocol-status dropdown like presign instead of assert.doesNotMatch(filters, /createTimeRange/) assert.match(genericList, /inlineDropdownFilters/) assert.match(genericList, /market-list__filter-row/) + assert.match(genericList, /showProtocolProjectFilter/) + assert.match(genericList, /全部项目/) + assert.doesNotMatch(genericList, /ProjectAssignmentField/) }) test('protocol list exposes mobile batch selection and sequential authorized deletion', () => { diff --git a/mobile-oa-h5/src/market/modules/protocol.js b/mobile-oa-h5/src/market/modules/protocol.js index 94956c46..d932c192 100644 --- a/mobile-oa-h5/src/market/modules/protocol.js +++ b/mobile-oa-h5/src/market/modules/protocol.js @@ -478,6 +478,12 @@ export default { allText: '全部协议状态', source: 'cell_storage', options: CELL_PROTOCOL_STATUS_OPTIONS.map((item) => ({ text: item.label, value: item.value })) + }, + { + key: 'protocolStatus', + allText: '全部协议状态', + source: 'research_donation', + options: DONATION_PROTOCOL_STATUS_OPTIONS.map((item) => ({ text: item.label, value: item.value })) } ], statusLabel(detail) { diff --git a/mobile-oa-h5/src/views/market/MarketModuleList.vue b/mobile-oa-h5/src/views/market/MarketModuleList.vue index b55d002f..33eb1823 100644 --- a/mobile-oa-h5/src/views/market/MarketModuleList.vue +++ b/mobile-oa-h5/src/views/market/MarketModuleList.vue @@ -6,8 +6,6 @@ :show-header="false" >