From bee2c0d19363f8457bf591952b76f340842b759f Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Tue, 13 Oct 2026 13:14:09 +0800 Subject: [PATCH] feat(backend): persist monthly verify no and expose research list fields Co-authored-by: Cursor --- backend/app/crud/research_donation.py | 46 ++- backend/app/services/market_protocol.py | 453 +++++++++++++++++++++++- 2 files changed, 485 insertions(+), 14 deletions(-) diff --git a/backend/app/crud/research_donation.py b/backend/app/crud/research_donation.py index 5e625751..1c8ada09 100644 --- a/backend/app/crud/research_donation.py +++ b/backend/app/crud/research_donation.py @@ -144,8 +144,6 @@ def next_donation_sample_code( return f"{prefix}{str(max_seq + 1).zfill(_DONATION_SAMPLE_CODE_SEQ_LEN)}" - - def allocate_monthly_verify_no( db: Session, *, @@ -299,6 +297,7 @@ def _donation_to_dict( return { "id": d.id, "donationNo": d.donation_no or "", + "monthlyVerifyNo": d.monthly_verify_no or "", "donationStatus": d.donation_status or "", "donationDate": (d.donation_date or "").strip() if d.donation_date else "", "sourceType": d.source_type or "direct", @@ -778,6 +777,7 @@ def list_donations( d["newbornSex"] = pc.get("newbornSex", "") d["deliveryMode"] = pc.get("deliveryMode", "") d["newbornWeightGram"] = pc.get("newbornWeightGram") + d["birthOrder"] = pc.get("birthOrder") else: d["motherName"] = "" d["motherPhone"] = "" @@ -789,6 +789,7 @@ def list_donations( d["newbornSex"] = "" d["deliveryMode"] = "" d["newbornWeightGram"] = None + d["birthOrder"] = None ci = collection_map.get(r.id) if ci: d["regionName"] = region_name_map.get(ci.get("regionId", ""), "") @@ -798,12 +799,14 @@ def list_donations( ci.get("regionManagerUserId", ""), "" ) d["sampleCollectTime"] = ci.get("sampleCollectTime", "") + d["maternalBloodCollectTime"] = ci.get("maternalBloodCollectTime", "") else: d["regionName"] = "" d["hospitalName"] = "" d["salesUserName"] = "" d["regionManagerUserName"] = "" d["sampleCollectTime"] = "" + d["maternalBloodCollectTime"] = "" donation_samples = sample_map.get(r.id, []) type_names: List[str] = [] codes: List[str] = [] @@ -817,7 +820,9 @@ def list_donations( codes.append(code) transport_obj = transport_by_sample_id.get(s.id) transport_dict: Optional[Dict[str, Any]] = None - if transport_obj and (transport_obj.transport_type or "").strip(): + if transport_obj and ( + (transport_obj.transport_type or "").strip() or transport_obj.eta_time + ): dest_site_id = transport_obj.dest_site_id or "" express_company_id = transport_obj.express_company_id or "" transport_dict = { @@ -1036,9 +1041,13 @@ def create_donation( ) now = datetime.now() + monthly_verify_no = allocate_monthly_verify_no( + db, tenant_no=tenant_no, create_time=now + ) obj = BizResearchDonation( id=generate_uuid(), donation_no=donation_no, + monthly_verify_no=monthly_verify_no, donation_status=(raw.get("donation_status") or "dsx").strip(), donation_date=(raw.get("donation_date") or "").strip() or None, source_type=(raw.get("source_type") or "direct").strip(), @@ -1634,11 +1643,15 @@ def convert_from_storage( now = datetime.now() donation_no = pn_src + monthly_verify_no = allocate_monthly_verify_no( + db, tenant_no=tenant_no, create_time=now + ) # 创建主记录 donation = BizResearchDonation( id=generate_uuid(), donation_no=donation_no, + monthly_verify_no=monthly_verify_no, donation_status="dsx", donation_date=now.strftime("%Y%m%d"), source_type="from_storage", @@ -2288,7 +2301,26 @@ def _create_samples( db.flush() result.append(obj) - # 本阶段不再写入旧运输明细;运费由 apply_freight_on_protocol_save 挂钩 + # 预计到达等仍写运输表;运费由 apply_freight_on_protocol_save 挂钩 + transport_raw = raw.get("transport") if isinstance(raw.get("transport"), dict) else {} + if not transport_raw and raw.get("eta_time"): + transport_raw = {"eta_time": raw.get("eta_time")} + elif isinstance(transport_raw, dict) and raw.get("eta_time") and not ( + transport_raw.get("eta_time") or transport_raw.get("etaTime") + ): + transport_raw = {**transport_raw, "eta_time": raw.get("eta_time")} + if transport_raw: + _create_transport( + db, + donation_id, + obj.id, + tenant_no, + user_id, + transport_raw, + idx, + now, + sample_code=obj.sample_code, + ) return result @@ -2304,14 +2336,14 @@ def _create_transport( now: datetime, sample_code: Optional[str] = None, ) -> Optional[BizResearchDonationSampleTransport]: + """写回捐赠样本运输;可仅有预计到达时间(运费批次改造后)。""" tnorm = _normalize_fields(raw) transport_type = (tnorm.get("transport_type") or "").strip() - if not transport_type: - return None - eta_time = tnorm.get("eta_time") if isinstance(eta_time, str): eta_time = parse_datetime(eta_time) + if not transport_type and not eta_time: + return None express_fee = tnorm.get("express_fee") if express_fee is not None and express_fee != "": diff --git a/backend/app/services/market_protocol.py b/backend/app/services/market_protocol.py index aa7c1844..045fd8a5 100644 --- a/backend/app/services/market_protocol.py +++ b/backend/app/services/market_protocol.py @@ -22,6 +22,7 @@ from app.models.models import ( BizProtocolCollectionInfo, BizProtocolPartyCustomer, BizProtocolSample, + BizProtocolSampleTransport, BizSampleCode, SysUser, ) @@ -30,6 +31,7 @@ from app.models.research_donation import ( BizResearchDonationCollection, BizResearchDonationParty, BizResearchDonationSample, + BizResearchDonationSampleTransport, ) from app.schemas.market_mobile import MarketProtocolBody from app.services.market_field_scope import ( @@ -39,7 +41,7 @@ from app.services.market_field_scope import ( assert_cell_protocol_access, assert_donation_access, ) -from app.services.market_support import validate_attachment_refs +from app.services.market_support import allowed_actions, validate_attachment_refs SOURCE_CELL = 'cell_storage' SOURCE_DONATION = 'research_donation' @@ -405,6 +407,203 @@ def _party_name_donation(db: Session, ids: List[str], tenant_no: str) -> dict: return {row.research_donation_id: row.mother_name or '' for row in rows} +def _enrich_protocol_cards( + db: Session, items: List[dict], tenant_no: str +) -> None: + """批量补齐与 Web 列表一致的移动卡片字段,避免逐行查询。""" + cell_ids = [item['id'] for item in items if item['source'] == SOURCE_CELL] + donation_ids = [ + item['id'] for item in items if item['source'] == SOURCE_DONATION + ] + collection_map = {} + sample_map = {} + party_map = {} + if cell_ids: + for row in db.query(BizProtocolCollectionInfo).filter( + BizProtocolCollectionInfo.collection_protocol_id.in_(cell_ids), + BizProtocolCollectionInfo.tenant_no == tenant_no, + BizProtocolCollectionInfo.del_flag == 0, + ).order_by(BizProtocolCollectionInfo.create_time.desc()).all(): + collection_map.setdefault((SOURCE_CELL, row.collection_protocol_id), row) + for row in db.query(BizProtocolSample).filter( + BizProtocolSample.collection_protocol_id.in_(cell_ids), + BizProtocolSample.tenant_no == tenant_no, + BizProtocolSample.del_flag == 0, + ).order_by(BizProtocolSample.create_time.asc()).all(): + sample_map.setdefault((SOURCE_CELL, row.collection_protocol_id), row) + for row in db.query(BizProtocolPartyCustomer).filter( + BizProtocolPartyCustomer.collection_protocol_id.in_(cell_ids), + BizProtocolPartyCustomer.tenant_no == tenant_no, + BizProtocolPartyCustomer.del_flag == 0, + ).order_by(BizProtocolPartyCustomer.create_time.desc()).all(): + party_map.setdefault((SOURCE_CELL, row.collection_protocol_id), row) + if donation_ids: + for row in db.query(BizResearchDonationCollection).filter( + BizResearchDonationCollection.research_donation_id.in_(donation_ids), + BizResearchDonationCollection.tenant_no == tenant_no, + BizResearchDonationCollection.del_flag == 0, + ).order_by(BizResearchDonationCollection.create_time.desc()).all(): + collection_map.setdefault((SOURCE_DONATION, row.research_donation_id), row) + for row in db.query(BizResearchDonationSample).filter( + BizResearchDonationSample.research_donation_id.in_(donation_ids), + BizResearchDonationSample.tenant_no == tenant_no, + BizResearchDonationSample.del_flag == 0, + ).order_by(BizResearchDonationSample.create_time.asc()).all(): + sample_map.setdefault((SOURCE_DONATION, row.research_donation_id), row) + for row in db.query(BizResearchDonationParty).filter( + BizResearchDonationParty.research_donation_id.in_(donation_ids), + BizResearchDonationParty.tenant_no == tenant_no, + BizResearchDonationParty.del_flag == 0, + ).order_by(BizResearchDonationParty.create_time.desc()).all(): + party_map.setdefault((SOURCE_DONATION, row.research_donation_id), row) + region_ids = { + row.region_id for row in collection_map.values() if row.region_id + } + hospital_ids = { + row.hospital_id for row in collection_map.values() if row.hospital_id + } + user_ids = { + value + for row in collection_map.values() + for value in (row.sales_user_id, row.region_manager_user_id) + if value + } + user_ids.update( + item.get('collectorUserId') + for item in items + if item.get('collectorUserId') + ) + type_ids = { + row.sample_type_id for row in sample_map.values() if row.sample_type_id + } + region_names = { + row.id: row.region_name or '' + for row in db.query(BizCollectionRegion).filter( + BizCollectionRegion.id.in_(region_ids or ['']), + BizCollectionRegion.tenant_no == tenant_no, + BizCollectionRegion.del_flag == 0, + ) + } + hospital_names = { + row.id: row.hospital_name or '' + for row in db.query(BizCollectionHospital).filter( + BizCollectionHospital.id.in_(hospital_ids or ['']), + BizCollectionHospital.tenant_no == tenant_no, + BizCollectionHospital.del_flag == 0, + ) + } + user_names = { + row.id: row.nick_name or '' + for row in db.query(SysUser).filter( + SysUser.id.in_(user_ids or ['']), + SysUser.tenant_no == tenant_no, + ) + } + type_names = { + row.id: row.type_name or '' + for row in db.query(BizCellSampleType).filter( + BizCellSampleType.id.in_(type_ids or ['']), + BizCellSampleType.del_flag == 0, + or_( + BizCellSampleType.tenant_no == tenant_no, + BizCellSampleType.tenant_no == '', + ), + ) + } + sample_ids = [row.id for row in sample_map.values()] + transport_by_sample = {} + if sample_ids: + for row in db.query(BizProtocolSampleTransport).filter( + BizProtocolSampleTransport.sample_id.in_(sample_ids), + BizProtocolSampleTransport.tenant_no == tenant_no, + BizProtocolSampleTransport.del_flag == 0, + ).all(): + transport_by_sample.setdefault(row.sample_id, row) + for row in db.query(BizResearchDonationSampleTransport).filter( + BizResearchDonationSampleTransport.sample_id.in_(sample_ids), + BizResearchDonationSampleTransport.tenant_no == tenant_no, + BizResearchDonationSampleTransport.del_flag == 0, + ).all(): + transport_by_sample.setdefault(row.sample_id, row) + lab_by_key = {} + from app.crud import sample_reception as reception_crud + + for source in SOURCES: + codes = [ + row.sample_code + for (item_source, _), row in sample_map.items() + if item_source == source and row.sample_code + ] + for code, lab_code in reception_crud.get_lab_storage_code_map_by_sample_codes( + db, tenant_no, codes, reception_source=source + ).items(): + lab_by_key[(source, code)] = lab_code + payment_summaries = {} + if cell_ids: + from app.services.qc_work_order_fee_summary import ( + build_protocol_payment_summaries, + ) + + payment_summaries = build_protocol_payment_summaries( + db, cell_ids, tenant_no + ) + transport_labels = {'kdys': '快递运输', 'ryxd': '人员携带'} + for item in items: + key = (item['source'], item['id']) + collection = collection_map.get(key) + sample = sample_map.get(key) + party = party_map.get(key) + payment_summary = payment_summaries.get(item['id']) or {} + transport = transport_by_sample.get(getattr(sample, 'id', '')) + sample_code = getattr(sample, 'sample_code', '') or '' + region_manager_name = user_names.get( + getattr(collection, 'region_manager_user_id', ''), '' + ) + item.update( + motherName=item.get('customerName', ''), + motherPhone=getattr(party, 'mother_phone', '') or '', + motherIdCard=getattr(party, 'mother_id_card', '') or '', + fatherName=getattr(party, 'father_name', '') or '', + newbornSex=getattr(party, 'newborn_sex', '') or '', + deliveryMode=getattr(party, 'delivery_mode', '') or '', + newbornWeightGram=getattr(party, 'newborn_weight_gram', None), + birthOrder=getattr(party, 'birth_order', None), + newbornDeliveryTime=_fmt_dt( + getattr(party, 'newborn_delivery_time', None) + ), + protocolStatus=item.get('status', ''), + donationStatus=item.get('status', ''), + regionName=region_names.get(getattr(collection, 'region_id', ''), ''), + hospitalName=hospital_names.get( + getattr(collection, 'hospital_id', ''), '' + ), + sampleCollectTime=_fmt_dt( + getattr(collection, 'sample_collect_time', None) + ), + maternalBloodCollectTime=_fmt_dt( + getattr(collection, 'maternal_blood_collect_time', None) + ), + salesUserName=user_names.get( + getattr(collection, 'sales_user_id', ''), '' + ), + collectorUserName=user_names.get(item.get('collectorUserId', ''), ''), + managerName=region_manager_name, + regionManagerUserName=region_manager_name, + sampleCode=sample_code, + sampleTypeName=type_names.get( + getattr(sample, 'sample_type_id', ''), '' + ), + coStored=bool(getattr(sample, 'is_special', 0)), + labStorageCode=lab_by_key.get((item['source'], sample_code), ''), + payMode=payment_summary.get('payMode', ''), + firstPayTime=payment_summary.get('firstPayTransferTime', ''), + transportMode=getattr(transport, 'transport_type', '') or '', + transportModeLabel=transport_labels.get( + getattr(transport, 'transport_type', '') or '', '' + ), + ) + + def list_protocols( db: Session, *, @@ -426,7 +625,26 @@ def list_protocols( db.query(BizCollectionProtocol), db, ctx, user_info=user_info ) if search: - query = query.filter(BizCollectionProtocol.protocol_no.like(f'%{search}%')) + like = f'%{search}%' + party_ids = db.query( + BizProtocolPartyCustomer.collection_protocol_id + ).filter( + BizProtocolPartyCustomer.tenant_no == ctx.tenant_no, + BizProtocolPartyCustomer.del_flag == 0, + BizProtocolPartyCustomer.mother_name.like(like), + ) + sample_ids = db.query(BizProtocolSample.collection_protocol_id).filter( + BizProtocolSample.tenant_no == ctx.tenant_no, + BizProtocolSample.del_flag == 0, + BizProtocolSample.sample_code.like(like), + ) + query = query.filter( + or_( + BizCollectionProtocol.protocol_no.like(like), + BizCollectionProtocol.id.in_(party_ids), + BizCollectionProtocol.id.in_(sample_ids), + ) + ) total += int( query.order_by(None) .with_entities(func.count(BizCollectionProtocol.id)) @@ -451,6 +669,10 @@ def list_protocols( 'code': row.protocol_no or '', 'protocolNo': row.protocol_no or '', 'status': row.protocol_status or '', + 'collectDate': row.register_date or '', + 'registerDate': row.register_date or '', + 'protocolTotalAmount': str(row.protocol_total_amount or ''), + 'collectorUserId': row.collector_user_id or '', 'customerName': names.get(row.id, ''), 'createTime': _fmt_dt(row.create_time), '_created': row.create_time or datetime.min, @@ -462,7 +684,28 @@ def list_protocols( db.query(BizResearchDonation), db, ctx, user_info=user_info ) if search: - query = query.filter(BizResearchDonation.donation_no.like(f'%{search}%')) + like = f'%{search}%' + party_ids = db.query( + BizResearchDonationParty.research_donation_id + ).filter( + BizResearchDonationParty.tenant_no == ctx.tenant_no, + BizResearchDonationParty.del_flag == 0, + BizResearchDonationParty.mother_name.like(like), + ) + sample_ids = db.query( + BizResearchDonationSample.research_donation_id + ).filter( + BizResearchDonationSample.tenant_no == ctx.tenant_no, + BizResearchDonationSample.del_flag == 0, + BizResearchDonationSample.sample_code.like(like), + ) + query = query.filter( + or_( + BizResearchDonation.donation_no.like(like), + BizResearchDonation.id.in_(party_ids), + BizResearchDonation.id.in_(sample_ids), + ) + ) total += int( query.order_by(None) .with_entities(func.count(BizResearchDonation.id)) @@ -486,9 +729,19 @@ def list_protocols( 'source': SOURCE_DONATION, 'code': row.donation_no or '', 'protocolNo': row.donation_no or '', + 'donationNo': row.donation_no or '', 'status': row.donation_status or '', + 'collectDate': row.donation_date or '', + 'donationDate': row.donation_date or '', 'customerName': names.get(row.id, ''), + 'collectorUserId': row.collector_user_id or '', 'createTime': _fmt_dt(row.create_time), + 'monthlyVerifyNo': row.monthly_verify_no or '', + 'voidDesc': row.void_desc or '', + 'consentFormUrls': row.consent_form_urls or '', + 'infectiousDiseaseReportUrls': row.infectious_disease_report_urls or '', + 'attachmentUrls': row.attachment_urls or '', + 'remark': row.remark or '', '_created': row.create_time or datetime.min, } for row in donation_rows @@ -501,22 +754,42 @@ def list_protocols( page_rows = rows[(safe_page - 1) * safe_size : safe_page * safe_size] for item in page_rows: item.pop('_created', None) + _enrich_protocol_cards(db, page_rows, ctx.tenant_no) return page_rows, total +def _attach_sample_eta( + sample_dicts: List[dict], + transport_by_sample_id: dict, +) -> List[dict]: + for item in sample_dicts: + transport = transport_by_sample_id.get(str(item.get('id') or '')) + eta = '' + if transport is not None: + eta = cell_crud._fmt_dt(getattr(transport, 'eta_time', None)) + item['etaTime'] = eta + item['transport'] = {'etaTime': eta} if eta else None + return sample_dicts + + def _cell_detail(db: Session, row: BizCollectionProtocol, tenant_no: str) -> dict: party = cell_crud.get_party_customer(db, row.id, tenant_no) collection = cell_crud.get_collection_info(db, row.id, tenant_no) samples = cell_crud.list_samples(db, row.id, tenant_no) payment = cell_crud.get_payment_by_protocol_id(db, row.id, tenant_no) - return { + transports = cell_crud.list_sample_transports(db, row.id, tenant_no) + transport_by_sample_id = {str(t.sample_id): t for t in transports} + detail = { 'source': SOURCE_CELL, 'basic': cell_crud._protocol_to_dict(row), 'collection': cell_crud._collection_info_to_dict(collection) if collection else None, 'customer': cell_crud._party_customer_to_dict(party) if party else None, - 'samples': [cell_crud._sample_to_dict(item) for item in samples], + 'samples': _attach_sample_eta( + [cell_crud._sample_to_dict(item) for item in samples], + transport_by_sample_id, + ), 'payment': cell_crud._payment_to_dict(payment) if payment else None, 'attachments': { 'protocolAttachments': row.protocol_attachments or '', @@ -524,6 +797,8 @@ def _cell_detail(db: Session, row: BizCollectionProtocol, tenant_no: str) -> dic 'healthSurveyFormUrls': row.health_survey_form_urls or '', }, } + _enrich_protocol_detail_labels(db, detail, tenant_no) + return detail def _donation_detail( @@ -532,14 +807,19 @@ def _donation_detail( party = donation_crud.get_party(db, row.id, tenant_no) collection = donation_crud.get_collection(db, row.id, tenant_no) samples = donation_crud.list_samples(db, row.id, tenant_no) - return { + transports = donation_crud.list_sample_transports(db, row.id, tenant_no) + transport_by_sample_id = {str(t.sample_id): t for t in transports} + detail = { 'source': SOURCE_DONATION, 'basic': donation_crud._donation_to_dict(row), 'collection': donation_crud._collection_to_dict(collection) if collection else None, 'customer': donation_crud._party_to_dict(party) if party else None, - 'samples': [donation_crud._sample_to_dict(item) for item in samples], + 'samples': _attach_sample_eta( + [donation_crud._sample_to_dict(item) for item in samples], + transport_by_sample_id, + ), 'attachments': { 'consentFormUrls': row.consent_form_urls or '', 'attachmentUrls': row.attachment_urls or '', @@ -547,6 +827,99 @@ def _donation_detail( or '', }, } + _enrich_protocol_detail_labels(db, detail, tenant_no) + return detail + + +def _enrich_protocol_detail_labels( + db: Session, detail: dict, tenant_no: str +) -> None: + """补齐 Web 详情使用的主数据名称,移动端不直接展示内部 ID。""" + basic = detail.get('basic') or {} + collection = detail.get('collection') or {} + samples = detail.get('samples') or [] + + region_id = str(collection.get('regionId') or '').strip() + hospital_id = str(collection.get('hospitalId') or '').strip() + if region_id: + region = ( + db.query(BizCollectionRegion) + .filter( + BizCollectionRegion.id == region_id, + BizCollectionRegion.tenant_no == tenant_no, + ) + .first() + ) + collection['regionName'] = region.region_name if region else '' + if hospital_id: + hospital = ( + db.query(BizCollectionHospital) + .filter( + BizCollectionHospital.id == hospital_id, + BizCollectionHospital.tenant_no == tenant_no, + ) + .first() + ) + collection['hospitalName'] = hospital.hospital_name if hospital else '' + + user_ids = { + str(value or '').strip() + for value in ( + basic.get('collectorUserId'), + collection.get('salesUserId'), + collection.get('regionManagerUserId'), + ) + if str(value or '').strip() + } + user_names = { + row.id: row.nick_name or '' + for row in db.query(SysUser).filter( + SysUser.id.in_(user_ids or ['']), + SysUser.tenant_no == tenant_no, + ) + } + basic['collectorUserName'] = user_names.get( + str(basic.get('collectorUserId') or ''), '' + ) + collection['salesUserName'] = user_names.get( + str(collection.get('salesUserId') or ''), '' + ) + collection['regionManagerUserName'] = user_names.get( + str(collection.get('regionManagerUserId') or ''), '' + ) + + type_ids = { + str(item.get('sampleTypeId') or '').strip() + for item in samples + if str(item.get('sampleTypeId') or '').strip() + } + type_names = { + row.id: row.type_name or '' + for row in db.query(BizCellSampleType).filter( + BizCellSampleType.id.in_(type_ids or ['']), + or_( + BizCellSampleType.tenant_no == tenant_no, + BizCellSampleType.tenant_no == '', + ), + ) + } + unit_ids = { + str(item.get('unitId') or '').strip() + for item in samples + if str(item.get('unitId') or '').strip() + } + unit_names = { + row.id: row.unit_name or '' + for row in db.query(BizItemUnit).filter( + BizItemUnit.id.in_(unit_ids or ['']), + BizItemUnit.tenant_no == tenant_no, + ) + } + for item in samples: + item['sampleTypeName'] = type_names.get( + str(item.get('sampleTypeId') or ''), '' + ) + item['unitName'] = unit_names.get(str(item.get('unitId') or ''), '') def get_protocol( @@ -588,6 +961,72 @@ def get_protocol( raise ValueError('不支持的协议来源') +def audit_protocol( + db: Session, + *, + source: str, + protocol_id: str, + audit_status: str, + audit_remark: Optional[str], + ctx: MarketFieldContext, + user_info: dict, +) -> dict: + if source == SOURCE_CELL: + row = assert_cell_protocol_access(db, protocol_id, ctx, user_info=user_info) + if 'audit' not in allowed_actions('protocol', status=row.protocol_status or ''): + raise ValueError('当前状态不可审核') + cell_crud.create_audit( + db, + collection_protocol_id=protocol_id, + audit_status=audit_status, + audit_remark=audit_remark, + tenant_no=ctx.tenant_no, + user_id=ctx.user_id, + ) + return {'id': protocol_id, 'source': source, 'protocolNo': row.protocol_no or ''} + if source == SOURCE_DONATION: + row = assert_donation_access(db, protocol_id, ctx, user_info=user_info) + if 'audit' not in allowed_actions('protocol', status=row.donation_status or ''): + raise ValueError('当前状态不可审核') + donation_crud.submit_audit( + db, + protocol_id, + audit_status=audit_status, + audit_remark=audit_remark, + tenant_no=ctx.tenant_no, + user_id=ctx.user_id, + ) + return {'id': protocol_id, 'source': source, 'donationNo': row.donation_no or ''} + raise ValueError('不支持的协议来源') + + +def delete_protocol( + db: Session, + *, + source: str, + protocol_id: str, + ctx: MarketFieldContext, + user_info: dict, +) -> None: + if source == SOURCE_CELL: + row = assert_cell_protocol_access(db, protocol_id, ctx, user_info=user_info) + if 'delete' not in allowed_actions('protocol', status=row.protocol_status or ''): + raise ValueError('当前状态不可删除') + cell_crud.soft_delete_collection_protocol( + db, protocol_id, ctx.tenant_no, user_id=ctx.user_id + ) + return + if source == SOURCE_DONATION: + row = assert_donation_access(db, protocol_id, ctx, user_info=user_info) + if 'delete' not in allowed_actions('protocol', status=row.donation_status or ''): + raise ValueError('当前状态不可删除') + donation_crud.delete_donation( + db, protocol_id, ctx.tenant_no, user_id=ctx.user_id + ) + return + raise ValueError('不支持的协议来源') + + def create_protocol( db: Session, *,