From be22c37196ed43ac89783f9849efbe9aee1b2726 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Tue, 13 Oct 2026 15:23:24 +0800 Subject: [PATCH] feat(backend): persist and return samplePayments for market protocols Mobile protocol create/update saves v2 per-sample payments; detail includes samplePayments for edit hydration. Co-authored-by: Cursor --- backend/app/services/market_protocol.py | 44 ++++++++++++++++++++ backend/tests/test_market_protocol_api.py | 50 +++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/backend/app/services/market_protocol.py b/backend/app/services/market_protocol.py index 045fd8a5..1789f063 100644 --- a/backend/app/services/market_protocol.py +++ b/backend/app/services/market_protocol.py @@ -10,6 +10,7 @@ from sqlalchemy.orm import Session from app.core.utils import parse_datetime from app.crud import collection_protocol as cell_crud +from app.crud import protocol_sample_payment as psp_crud from app.crud import research_donation as donation_crud from app.models.models import ( BizCellSampleType, @@ -772,6 +773,37 @@ def _attach_sample_eta( return sample_dicts +def _persist_sample_payments_if_any( + db: Session, + *, + row: BizCollectionProtocol, + body: MarketProtocolBody, + ctx: MarketFieldContext, +) -> None: + """细胞存储:将 body.sample_payments 写入 v2 按样本缴费方案。""" + if body.source != SOURCE_CELL: + if body.sample_payments: + raise ValueError('科研捐赠不支持样本缴费方案') + return + if body.sample_payments is None: + return + version = int(getattr(row, 'payment_model_version', None) or 1) + if version < 2: + row.payment_model_version = 2 + db.add(row) + db.flush() + raw = [ + p.model_dump(by_alias=True, exclude_none=False) for p in body.sample_payments + ] + psp_crud.save_sample_payments_for_protocol( + db, + collection_protocol_id=row.id, + tenant_no=ctx.tenant_no, + user_id=ctx.user_id, + sample_payments=raw, + ) + + 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) @@ -779,6 +811,12 @@ def _cell_detail(db: Session, row: BizCollectionProtocol, tenant_no: str) -> dic payment = cell_crud.get_payment_by_protocol_id(db, row.id, tenant_no) transports = cell_crud.list_sample_transports(db, row.id, tenant_no) transport_by_sample_id = {str(t.sample_id): t for t in transports} + version = int(getattr(row, 'payment_model_version', None) or 1) + sample_payments = [] + if version >= 2: + sample_payments = psp_crud.get_sample_payments_bundle( + db, collection_protocol_id=row.id, tenant_no=tenant_no + ) detail = { 'source': SOURCE_CELL, 'basic': cell_crud._protocol_to_dict(row), @@ -791,6 +829,8 @@ def _cell_detail(db: Session, row: BizCollectionProtocol, tenant_no: str) -> dic transport_by_sample_id, ), 'payment': cell_crud._payment_to_dict(payment) if payment else None, + 'paymentModelVersion': version, + 'samplePayments': sample_payments, 'attachments': { 'protocolAttachments': row.protocol_attachments or '', 'consentFormUrls': row.consent_form_urls or '', @@ -1100,11 +1140,14 @@ def create_protocol( create_dept_id=dept_id, commit=False, ) + _persist_sample_payments_if_any(db, row=row, body=body, ctx=ctx) assert_cell_protocol_access(db, row.id, ctx, user_info=user_info) db.commit() db.refresh(row) return _cell_detail(db, row, ctx.tenant_no) + if body.sample_payments: + raise ValueError('科研捐赠不支持样本缴费方案') data = { 'donation_no': body.donation_no or '', 'donation_date': body.donation_date, @@ -1280,6 +1323,7 @@ def update_protocol( create_dept_id=dept_id, commit=False, ) + _persist_sample_payments_if_any(db, row=row, body=body, ctx=ctx) row = assert_cell_protocol_access( db, row.id, ctx, user_info=user_info ) diff --git a/backend/tests/test_market_protocol_api.py b/backend/tests/test_market_protocol_api.py index 875f0b24..cc04221f 100644 --- a/backend/tests/test_market_protocol_api.py +++ b/backend/tests/test_market_protocol_api.py @@ -23,8 +23,10 @@ from app.models.models import ( BizProtocolPaymentDetail, BizProtocolPaymentSchedule, BizProtocolPaymentTransfer, + BizProtocolSamplePayment, BizProtocolSample, BizProtocolSampleTransport, + BizPresignOrder, BizSampleCode, BizSampleReception, SysPost, @@ -57,8 +59,10 @@ def _session(): BizProtocolSampleTransport.__table__, BizProtocolPayment.__table__, BizProtocolPaymentDetail.__table__, + BizProtocolSamplePayment.__table__, BizProtocolPaymentSchedule.__table__, BizProtocolPaymentTransfer.__table__, + BizPresignOrder.__table__, BizSampleCode.__table__, BizSampleReception.__table__, BizResearchDonation.__table__, @@ -844,6 +848,52 @@ def test_market_protocol_body_accepts_sample_payments(): assert body.sample_payments[0].pay_mode == 'dj' +def test_protocol_create_persists_sample_payments(): + from app.services.market_protocol import create_protocol, get_protocol + + db = _session() + _masters(db) + body = _body( + samplePayments=[ + { + 'sampleCode': 'S-001', + 'payMode': 'dj', + 'contractAmount': 20000, + 'lumpSumDeadline': '20261231', + 'schedules': [ + { + 'scheduleNo': 1, + 'dueAmount': 20000, + 'dueDate': '20261231', + 'scheduleStatus': 'open', + 'transfers': [], + } + ], + } + ] + ) + created = create_protocol( + db, body=body, ctx=_ctx('specialist'), user_info=_user() + ) + protocol_id = created['basic']['id'] + pays = created.get('samplePayments') or [] + assert len(pays) == 1 + assert pays[0]['sampleCode'] == 'S-001' + assert pays[0]['payMode'] == 'dj' + assert float(pays[0]['contractAmount']) == 20000 + assert str(pays[0]['schedules'][0].get('dueDate') or '').replace('-', '')[:8] == '20261231' + + detail = get_protocol( + db, + source='cell_storage', + protocol_id=protocol_id, + ctx=_ctx('specialist'), + user_info=_user(), + ) + assert len(detail.get('samplePayments') or []) == 1 + assert detail['samplePayments'][0]['sampleCode'] == 'S-001' + + def test_protocol_body_rejects_status_finance_and_conversion_fields(): from app.schemas.market_mobile import MarketProtocolBody