From 9c61f36db8a948c1fbddcb6b149c52cf67c04b1b Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Tue, 13 Oct 2026 15:21:07 +0800 Subject: [PATCH] feat(backend): accept samplePayments on market protocol body Whitelist per-sample payment payload for mobile protocol create/update. Co-authored-by: Cursor --- backend/app/schemas/market_mobile.py | 40 +++++++++++++++++++++++ backend/tests/test_market_protocol_api.py | 31 ++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/backend/app/schemas/market_mobile.py b/backend/app/schemas/market_mobile.py index 25e53037..01887409 100644 --- a/backend/app/schemas/market_mobile.py +++ b/backend/app/schemas/market_mobile.py @@ -247,6 +247,43 @@ class MarketProtocolAttachmentsBody(BaseModel): ) +class MarketProtocolPaymentScheduleBody(BaseModel): + model_config = ConfigDict(extra='forbid', populate_by_name=True) + + id: Optional[str] = None + schedule_no: Optional[int] = Field(None, alias='scheduleNo') + due_amount: Optional[float] = Field(None, alias='dueAmount') + due_date: Optional[str] = Field(None, alias='dueDate') + schedule_status: Optional[str] = Field(None, alias='scheduleStatus') + transfers: Optional[List[dict]] = None + + +class MarketProtocolSamplePaymentBody(BaseModel): + model_config = ConfigDict(extra='forbid', populate_by_name=True) + + id: Optional[str] = None + sample_code: str = Field(..., min_length=1, alias='sampleCode') + protocol_sample_id: Optional[str] = Field(None, alias='protocolSampleId') + pay_mode: str = Field(..., alias='payMode') + contract_amount: Optional[float] = Field(None, alias='contractAmount') + lump_sum_deadline: Optional[str] = Field(None, alias='lumpSumDeadline') + installment_count: Optional[int] = Field(None, alias='installmentCount') + installment_interval_months: Optional[float] = Field( + None, alias='installmentIntervalMonths' + ) + installment_start_date: Optional[str] = Field( + None, alias='installmentStartDate' + ) + installment_advance_days: Optional[int] = Field( + None, alias='installmentAdvanceDays' + ) + installment_ratios: Optional[List[float]] = Field( + None, alias='installmentRatios' + ) + remark: Optional[str] = None + schedules: Optional[List[MarketProtocolPaymentScheduleBody]] = None + + class MarketProtocolBody(BaseModel): """移动协议白名单;状态、审核、财务、退款及转换字段一律拒绝。""" @@ -266,6 +303,9 @@ class MarketProtocolBody(BaseModel): collection: Optional[MarketProtocolCollectionBody] = None samples: Optional[List[MarketProtocolSampleBody]] = None attachments: Optional[MarketProtocolAttachmentsBody] = None + sample_payments: Optional[List[MarketProtocolSamplePaymentBody]] = Field( + None, alias='samplePayments' + ) class MarketProtocolAuditBody(BaseModel): diff --git a/backend/tests/test_market_protocol_api.py b/backend/tests/test_market_protocol_api.py index e3bbce3b..875f0b24 100644 --- a/backend/tests/test_market_protocol_api.py +++ b/backend/tests/test_market_protocol_api.py @@ -813,6 +813,37 @@ def test_protocol_create_forbids_existing_customer_ids(source): ) +def test_market_protocol_body_accepts_sample_payments(): + from app.schemas.market_mobile import MarketProtocolBody + + body = MarketProtocolBody.model_validate( + { + 'source': 'cell_storage', + 'protocolNo': 'P-PAY-1', + 'customer': {'motherName': '李四'}, + 'samplePayments': [ + { + 'sampleCode': 'S-001', + 'payMode': 'dj', + 'contractAmount': 20000, + 'lumpSumDeadline': '20261231', + 'schedules': [ + { + 'scheduleNo': 1, + 'dueAmount': 20000, + 'dueDate': '20261231', + } + ], + } + ], + } + ) + assert body.sample_payments is not None + assert len(body.sample_payments) == 1 + assert body.sample_payments[0].sample_code == 'S-001' + assert body.sample_payments[0].pay_mode == 'dj' + + def test_protocol_body_rejects_status_finance_and_conversion_fields(): from app.schemas.market_mobile import MarketProtocolBody