fix(project): show sample codes in storage and donation assignments #20
No files matched your search
@@ -6,9 +6,11 @@ from sqlalchemy import and_, func, or_
|
||||
|
||||
from app.models.models import (
|
||||
BizChannelServiceOrder, BizCollectionHospital,
|
||||
BizPilotPatient, BizProtocolCollectionInfo, BizProtocolPartyCustomer, SysUser,
|
||||
BizPilotPatient, BizProtocolCollectionInfo, BizProtocolPartyCustomer, BizProtocolSample, SysUser,
|
||||
)
|
||||
from app.models.research_donation import (
|
||||
BizResearchDonationCollection, BizResearchDonationParty, BizResearchDonationSample,
|
||||
)
|
||||
from app.models.research_donation import BizResearchDonationCollection, BizResearchDonationParty
|
||||
from app.models.project_management import Project, ProjectTeam
|
||||
from app.services import project_management as projects
|
||||
|
||||
@@ -17,6 +19,10 @@ PARTY_MODELS = {
|
||||
'cell_storage': (BizProtocolPartyCustomer, BizProtocolCollectionInfo, 'collection_protocol_id'),
|
||||
'research_donation': (BizResearchDonationParty, BizResearchDonationCollection, 'research_donation_id'),
|
||||
}
|
||||
SAMPLE_MODELS = {
|
||||
'cell_storage': BizProtocolSample,
|
||||
'research_donation': BizResearchDonationSample,
|
||||
}
|
||||
|
||||
|
||||
def keyword_condition(db, tenant, kind, keyword):
|
||||
@@ -25,6 +31,11 @@ def keyword_condition(db, tenant, kind, keyword):
|
||||
conditions = [getattr(model, number).contains(keyword, autoescape=True)]
|
||||
if kind in PARTY_MODELS:
|
||||
party, collection, parent_key = PARTY_MODELS[kind]
|
||||
sample = SAMPLE_MODELS[kind]
|
||||
conditions.append(projects.scoped(db, sample, tenant).filter(
|
||||
getattr(sample, parent_key) == model.id,
|
||||
sample.sample_code.contains(keyword, autoescape=True),
|
||||
).exists())
|
||||
conditions.append(projects.scoped(db, party, tenant).filter(
|
||||
getattr(party, parent_key) == model.id,
|
||||
or_(party.mother_name.contains(keyword, autoescape=True),
|
||||
@@ -98,6 +109,7 @@ def business_details(db, tenant, kind, rows):
|
||||
if kind in PARTY_MODELS:
|
||||
party, collection, parent_key = PARTY_MODELS[kind]
|
||||
parties = child_rows(db, tenant, party, parent_key, ids, ('mother_name', 'mother_phone'))
|
||||
samples = child_rows(db, tenant, SAMPLE_MODELS[kind], parent_key, ids, ('sample_code',))
|
||||
collections = child_rows(db, tenant, collection, parent_key, ids,
|
||||
('hospital_id', 'sales_user_id', 'sample_collect_time'))
|
||||
all_collections = [item for values in collections.values() for item in values]
|
||||
@@ -112,6 +124,7 @@ def business_details(db, tenant, kind, rows):
|
||||
for row in rows:
|
||||
info = collections[row.id]
|
||||
details = dict(
|
||||
sampleCodes=joined_values(samples[row.id], 'sample_code'),
|
||||
motherName=joined_values(parties[row.id], 'mother_name'),
|
||||
motherPhone=joined_values(parties[row.id], 'mother_phone'),
|
||||
hospitalName='、'.join(dict.fromkeys(hospitals[item.hospital_id] for item in info
|
||||
|
||||
@@ -10,9 +10,11 @@ from app.models.base import Base
|
||||
from app.models.models import (
|
||||
BizChannelServiceOrder, BizCollectionHospital, BizCollectionProtocol,
|
||||
BizPilotApplication, BizPilotPatient, BizProtocolCollectionInfo,
|
||||
BizProtocolPartyCustomer, SysMenu, SysRole, SysRoleMenu, SysUser, SysUserRole,
|
||||
BizProtocolPartyCustomer, BizProtocolSample, SysMenu, SysRole, SysRoleMenu, SysUser, SysUserRole,
|
||||
)
|
||||
from app.models.research_donation import (
|
||||
BizResearchDonation, BizResearchDonationParty, BizResearchDonationSample,
|
||||
)
|
||||
from app.models.research_donation import BizResearchDonation, BizResearchDonationParty
|
||||
from app.services import project_assignment_records as svc
|
||||
from app.services import project_management
|
||||
|
||||
@@ -150,3 +152,38 @@ class ProjectAssignmentRecordsTest(unittest.TestCase):
|
||||
self.assertEqual(len(statements), small)
|
||||
finally:
|
||||
event.remove(self.engine, 'before_cursor_execute', count_statement)
|
||||
|
||||
def test_sample_codes_keep_one_main_row_and_respect_tenant_and_soft_delete(self):
|
||||
for kind, model, parent_key in (
|
||||
('cell_storage', BizProtocolSample, 'collection_protocol_id'),
|
||||
('research_donation', BizResearchDonationSample, 'research_donation_id'),
|
||||
):
|
||||
with self.subTest(kind=kind):
|
||||
if kind == 'cell_storage':
|
||||
parents = [BizCollectionProtocol(id=kind + suffix, tenant_no='t', protocol_no=suffix)
|
||||
for suffix in ('one', 'empty')]
|
||||
else:
|
||||
parents = [BizResearchDonation(id=kind + suffix, tenant_no='t', donation_no=suffix)
|
||||
for suffix in ('one', 'empty')]
|
||||
self.db.add_all(parents)
|
||||
for row_id, code, tenant, deleted in (
|
||||
('1', 'SAMPLE-A', 't', 0), ('2', 'SAMPLE-B', 't', 0),
|
||||
('3', 'SAMPLE-A', 't', 0), ('4', 'FOREIGN-SAMPLE', 'foreign', 0),
|
||||
('5', 'DELETED-SAMPLE', 't', 1), ('6', '', 't', 0), ('7', None, 't', 0),
|
||||
):
|
||||
self.db.add(model(id=kind + row_id, tenant_no=tenant, del_flag=deleted,
|
||||
sample_code=code, create_time=datetime(2026, 11, 4),
|
||||
**{parent_key: kind + 'one'}))
|
||||
self.db.commit()
|
||||
for keyword in ('SAMPLE-A', 'SAMPLE-B', 'SAMPLE-'):
|
||||
result = self.records(kind, keyword=keyword, pageSize=1)
|
||||
self.assertEqual(result['total'], 1)
|
||||
self.assertEqual(sum(result['statusCounts'].values()), 1)
|
||||
self.assertEqual(result['list'][0]['sampleCodes'], 'SAMPLE-A、SAMPLE-B')
|
||||
self.assertEqual(result['list'][0]['id'], kind + 'one')
|
||||
self.assertEqual(self.records(kind, keyword='SAMPLE-', page=2, pageSize=1)['list'], [])
|
||||
for keyword in ('FOREIGN-SAMPLE', 'DELETED-SAMPLE'):
|
||||
self.assertEqual(self.records(kind, keyword=keyword)['total'], 0)
|
||||
rows = {row['id']: row for row in self.records(kind)['list']}
|
||||
self.assertEqual(len(rows), 2)
|
||||
self.assertEqual(rows[kind + 'empty']['sampleCodes'], '')
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
## 历史归属维护 v2(2026-11-04 已确认)
|
||||
|
||||
后续用户明确调整:细胞存储、科研捐赠 tab 的单号列替换为“样本编码”,沿用已确认表格的列宽、位置及样式,不新增界面或视觉布局。专用接口新增只读 `sampleCodes`,按主单的同租户有效样本子行批量读取,多个编码稳定去重后用顿号连接,无样本时显示破折号;关键词增加样本编码 EXISTS 匹配。列表仍一张主单一行,归属操作仍作用于主单及关联链;主单编号保留在接口和原关联预览中供现有逻辑使用。此处仅读取本单样本子行,不涉及接收/质检等下游跨表匹配。
|
||||
|
||||
用户明确采用 [Figma 原型 v2](https://www.figma.com/design/wOQLDAhr1gdlQEXnVaFO9S?node-id=13-23)。范围为 Web 历史归属维护及其后端读取接口,沿用用户先合入 V2.0 再自测的安排。复用 P02 列表和 `pilot-service/pilot-application/index.vue` 的 `panel-pill-tabs`,操作列固定右侧,搜索右对齐。
|
||||
|
||||
`RecordList` 仅在 assignment 模式显示四类正式业务 tab 和业务识别列。细胞存储/科研捐赠展示母亲姓名、电话、采集医院、市场专员及采集/捐赠日期;中试展示申请名称、渠道、服务订单名称、患者姓名/电话;基础实验展示课题、客户名称/联系人/电话。公共列保留序号、主单号、状态、项目组、所属项目、创建时间。多个有效子记录以稳定顺序去重连接展示,缺失值显示破折号,采集日期来自采集信息的样本采集时间。
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
### Requirement: 按业务识别历史归属单据
|
||||
历史归属维护 SHALL 按细胞存储、科研捐赠、中试服务、基础实验展示四个 tab,并提供对应业务识别字段和关键词搜索;列表保持主单分页,右侧固定归属与审计操作。
|
||||
#### Scenario: 存储和捐赠以样本编码识别
|
||||
- **WHEN** 查看细胞存储或科研捐赠 tab
|
||||
- **THEN** 展示样本编码列而非协议单号/捐赠单号列;多个有效编码在同一主单行去重展示,无编码显示破折号,支持按任一编码搜索;跨租户或已删除的样本不用于展示和匹配,分页和归属仍以主单为单位。
|
||||
#### Scenario: 按联系人或业务信息定位
|
||||
- **WHEN** 用户在某类业务输入姓名、电话、医院/专员、渠道/服务订单或课题/客户等该业务支持的关键词
|
||||
- **THEN** 仅返回当前租户及数据权限内匹配的有效主单,展示对应业务信息,多条子记录不重复计数;删除和跨租户关联数据不用于匹配或展示。
|
||||
|
||||
@@ -32,6 +32,7 @@ export interface AssignmentRecord {
|
||||
teamId: string | null
|
||||
teamName: string | null
|
||||
createTime: string | null
|
||||
sampleCodes?: string
|
||||
motherName?: string
|
||||
motherPhone?: string
|
||||
hospitalName?: string
|
||||
|
||||
@@ -22,12 +22,12 @@ const collectionColumns: AssignmentColumn[] = [
|
||||
|
||||
export const assignmentColumns: Record<AssignmentBizType, AssignmentColumn[]> = {
|
||||
cell_storage: [
|
||||
{ prop: 'bizNo', label: '协议单号', minWidth: 180 },
|
||||
{ prop: 'sampleCodes', label: '样本编码', minWidth: 180 },
|
||||
...collectionColumns,
|
||||
{ prop: 'collectionDate', label: '采集日期', minWidth: 140 }
|
||||
],
|
||||
research_donation: [
|
||||
{ prop: 'bizNo', label: '捐赠单号', minWidth: 180 },
|
||||
{ prop: 'sampleCodes', label: '样本编码', minWidth: 180 },
|
||||
...collectionColumns,
|
||||
{ prop: 'donationDate', label: '捐赠日期', minWidth: 140 }
|
||||
],
|
||||
@@ -49,8 +49,8 @@ export const assignmentColumns: Record<AssignmentBizType, AssignmentColumn[]> =
|
||||
}
|
||||
|
||||
export const assignmentKeywordHints: Record<AssignmentBizType, string> = {
|
||||
cell_storage: '协议单号 / 母亲姓名、电话 / 医院 / 市场专员',
|
||||
research_donation: '捐赠单号 / 母亲姓名、电话 / 医院 / 市场专员',
|
||||
cell_storage: '样本编码 / 母亲姓名、电话 / 医院 / 市场专员',
|
||||
research_donation: '样本编码 / 母亲姓名、电话 / 医院 / 市场专员',
|
||||
pilot_service: '申请单号、名称 / 渠道 / 服务订单 / 患者姓名、电话',
|
||||
basic_experiment: '申请单号 / 课题 / 客户名称、联系人、电话'
|
||||
}
|
||||
Reference in new issue
Block a user