|
|
@@ -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,
|
|
|
+ )
|