feat(pl): add PUT plan side contracts for contract completion

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-08-20 12:09:42 +08:00
1 parent d1d7425eeb
commit b984c8c857
2 files changed
+94 -1

No files matched your search

+28 -1
View File
@@ -33,6 +33,7 @@ from app.schemas.pl.plan import (
PlanArchiveBody,
PlanArchiveBrandQtyItem,
PlanContractGapListResponse,
PlanContractGapRead,
PlanCreate,
PlanCreateFull,
PlanDriverFeedbackUpdateBody,
@@ -45,11 +46,12 @@ from app.schemas.pl.plan import (
PlanProductCertsUpdate,
PlanProductRead,
PlanRead,
PlanSideContractsUpdateBody,
PlanSiteFeedbackUpdateBody,
PlanUpdate,
PlanWithProducts,
)
from app.services.pl_plan_contract_gap import list_plan_contract_gaps
from app.services.pl_plan_contract_gap import list_plan_contract_gaps, update_plan_side_contracts
from app.services.pl_brand_archive import load_brand_qtys_by_plan_product_ids
from app.services.pl_product_display import (
load_crm_product_alias_maps,
@@ -551,6 +553,31 @@ def list_contract_gaps(
return PlanContractGapListResponse(total=total, list=items)
@router.put("/{id}/contracts/{side}", response_model=PlanContractGapRead)
def put_plan_side_contracts(
id: str,
side: str,
payload: PlanSideContractsUpdateBody,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> PlanContractGapRead:
"""单侧保存计划合同关联(合同补全)。"""
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
plan = _get_plan(db, tenant_no, id, user)
result = update_plan_side_contracts(
db,
tenant_no=tenant_no,
plan=plan,
side=side,
contracts=payload.contracts,
user_id=user.id,
dept_id=user.dept_id,
)
plan.update_by = user.id
db.commit()
return result
# =============================================================================
# 创建计划
# =============================================================================
@@ -3,6 +3,7 @@ from __future__ import annotations
from datetime import date
from typing import List, Optional, TYPE_CHECKING
from fastapi import HTTPException
from sqlalchemy import and_, exists, func, or_, select
from sqlalchemy.orm import Session
@@ -13,7 +14,10 @@ from app.services.pl_internal_customer import INTERNAL_CUSTOMER_NAMES, is_intern
from app.services.pl_plan_service import (
PLAN_CONTRACT_SIDE_DEMANDER,
PLAN_CONTRACT_SIDE_SUPPLIER,
_normalize_contract_links,
assert_contract_selectable_for_plan,
load_plan_contracts_grouped,
replace_plan_contracts,
)
if TYPE_CHECKING:
@@ -193,3 +197,65 @@ def list_plan_contract_gaps(
)
)
return total, items
def update_plan_side_contracts(
db: Session,
*,
tenant_no: str,
plan: PlPlan,
side: str,
contracts: list[PlanContractLink],
user_id: Optional[str],
dept_id: Optional[str],
) -> PlanContractGapRead:
"""单侧保存计划合同关联;允许空列表,不强制非内置侧必须有合同。"""
if plan.follow_status == "cancelled":
raise HTTPException(status_code=400, detail="已作废的计划不能修改")
if side not in (PLAN_CONTRACT_SIDE_SUPPLIER, PLAN_CONTRACT_SIDE_DEMANDER):
raise HTTPException(status_code=422, detail="side 必须为 supplier 或 demander")
current_s, current_d = load_plan_contracts_grouped(db, tenant_no, [plan.id]).get(
plan.id, ([], [])
)
links = _normalize_contract_links(contracts)
expected_customer_id = (
plan.supplier_id if side == PLAN_CONTRACT_SIDE_SUPPLIER else plan.customer_id
)
side_label = "供方合同" if side == PLAN_CONTRACT_SIDE_SUPPLIER else "需方合同"
resolved: list[PlanContractLink] = []
for link in links:
contract = assert_contract_selectable_for_plan(
db,
link.contract_id,
tenant_no,
expected_customer_id=expected_customer_id,
side_label=side_label,
)
resolved.append(
PlanContractLink(
contractId=contract.id,
contractNo=contract.contract_no or link.contract_no,
)
)
supplier_links = resolved if side == PLAN_CONTRACT_SIDE_SUPPLIER else current_s
demander_links = resolved if side == PLAN_CONTRACT_SIDE_DEMANDER else current_d
replace_plan_contracts(
db,
tenant_no=tenant_no,
plan_id=plan.id,
supplier_contracts=supplier_links,
demander_contracts=demander_links,
user_id=user_id,
dept_id=dept_id,
)
supplier_contracts, demander_contracts = load_plan_contracts_grouped(
db, tenant_no, [plan.id]
).get(plan.id, ([], []))
return build_plan_contract_gap_read(
plan,
supplier_contracts=supplier_contracts,
demander_contracts=demander_contracts,
)