diff --git a/backend/app/schemas/pl/plan.py b/backend/app/schemas/pl/plan.py
index c8ff5692d..efccc4258 100644
--- a/backend/app/schemas/pl/plan.py
+++ b/backend/app/schemas/pl/plan.py
@@ -430,6 +430,7 @@ class PlanContractGapRead(BaseModel):
id: str
plan_no: str = Field(..., alias="planNo")
plan_date: Optional[date] = Field(None, alias="planDate")
+ plan_type: Optional[str] = Field(None, alias="planType")
follow_status: str = Field(..., alias="followStatus")
supplier_id: Optional[str] = Field(None, alias="supplierId")
supplier_name: Optional[str] = Field(None, alias="supplierName")
@@ -440,6 +441,7 @@ class PlanContractGapRead(BaseModel):
demander_contract_exempt: bool = Field(..., alias="demanderContractExempt")
supplier_contracts: list[PlanContractLink] = Field(default_factory=list, alias="supplierContracts")
demander_contracts: list[PlanContractLink] = Field(default_factory=list, alias="demanderContracts")
+ products: list[PlanProductRead] = Field(default_factory=list, description="产品明细")
model_config = {"populate_by_name": True, "from_attributes": True}
diff --git a/backend/app/services/pl_plan_contract_gap.py b/backend/app/services/pl_plan_contract_gap.py
index 3a43b31a4..e1aa97de5 100644
--- a/backend/app/services/pl_plan_contract_gap.py
+++ b/backend/app/services/pl_plan_contract_gap.py
@@ -7,8 +7,8 @@ from fastapi import HTTPException
from sqlalchemy import and_, exists, func, or_, select
from sqlalchemy.orm import Session
-from app.models.pl_models import PlPlan, PlPlanContract
-from app.schemas.pl.plan import PlanContractGapRead, PlanContractLink
+from app.models.pl_models import PlPlan, PlPlanContract, PlPlanProduct
+from app.schemas.pl.plan import PlanContractGapRead, PlanContractLink, PlanProductRead
from app.services.pl_data_scope import apply_pl_scope
from app.services.pl_internal_customer import INTERNAL_CUSTOMER_NAMES, is_internal_customer_name
from app.services.pl_plan_service import (
@@ -90,6 +90,7 @@ def build_plan_contract_gap_read(
*,
supplier_contracts: list[PlanContractLink],
demander_contracts: list[PlanContractLink],
+ products: Optional[list[PlanProductRead]] = None,
) -> PlanContractGapRead:
gap_sides = compute_contract_gap_sides(
supplier_name=plan.supplier_name,
@@ -101,6 +102,7 @@ def build_plan_contract_gap_read(
id=plan.id,
planNo=plan.plan_no,
planDate=plan.plan_date,
+ planType=plan.plan_type,
followStatus=plan.follow_status,
supplierId=plan.supplier_id,
supplierName=plan.supplier_name,
@@ -111,9 +113,31 @@ def build_plan_contract_gap_read(
demanderContractExempt=side_is_exempt(plan.customer_name),
supplierContracts=supplier_contracts,
demanderContracts=demander_contracts,
+ products=list(products or []),
)
+def _load_products_by_plan_ids(
+ db: Session,
+ plan_ids: list[str],
+) -> dict[str, list[PlanProductRead]]:
+ if not plan_ids:
+ return {}
+ rows = (
+ db.query(PlPlanProduct)
+ .filter(
+ PlPlanProduct.plan_id.in_(plan_ids),
+ PlPlanProduct.del_flag == 0,
+ )
+ .order_by(PlPlanProduct.order_num.asc(), PlPlanProduct.create_time.asc())
+ .all()
+ )
+ out: dict[str, list[PlanProductRead]] = {pid: [] for pid in plan_ids}
+ for row in rows:
+ out.setdefault(row.plan_id, []).append(PlanProductRead.model_validate(row))
+ return out
+
+
def list_plan_contract_gaps(
db: Session,
*,
@@ -185,6 +209,7 @@ def list_plan_contract_gaps(
plan_ids = [plan.id for plan in plans]
contracts_by_plan = load_plan_contracts_grouped(db, tenant_no, plan_ids)
+ products_by_plan = _load_products_by_plan_ids(db, plan_ids)
items: list[PlanContractGapRead] = []
for plan in plans:
@@ -194,6 +219,7 @@ def list_plan_contract_gaps(
plan,
supplier_contracts=supplier_contracts,
demander_contracts=demander_contracts,
+ products=products_by_plan.get(plan.id, []),
)
)
return total, items
@@ -254,8 +280,10 @@ def update_plan_side_contracts(
supplier_contracts, demander_contracts = load_plan_contracts_grouped(
db, tenant_no, [plan.id]
).get(plan.id, ([], []))
+ products = _load_products_by_plan_ids(db, [plan.id]).get(plan.id, [])
return build_plan_contract_gap_read(
plan,
supplier_contracts=supplier_contracts,
demander_contracts=demander_contracts,
+ products=products,
)
diff --git a/web-admin/src/api/pl/index.ts b/web-admin/src/api/pl/index.ts
index e6874a8a9..2cce6fa74 100644
--- a/web-admin/src/api/pl/index.ts
+++ b/web-admin/src/api/pl/index.ts
@@ -658,6 +658,7 @@ export interface PlanContractGapItem {
id: string
planNo: string
planDate?: string
+ planType?: string
followStatus: string
supplierId?: string
supplierName?: string
@@ -668,6 +669,7 @@ export interface PlanContractGapItem {
demanderContractExempt: boolean
supplierContracts: PlanContractLink[]
demanderContracts: PlanContractLink[]
+ products?: PlanProduct[]
}
export interface PlanContractGapQuery {
diff --git a/web-admin/src/views/pl/plan-contract/index.vue b/web-admin/src/views/pl/plan-contract/index.vue
index b4c448969..dc4623631 100644
--- a/web-admin/src/views/pl/plan-contract/index.vue
+++ b/web-admin/src/views/pl/plan-contract/index.vue
@@ -195,11 +195,18 @@
+
+
+ {{ getPlanTypeLabel(row.planType) }}
+
+
@@ -214,6 +221,8 @@
label="需方"
min-width="200"
align="left"
+ class-name="col-name-wrap"
+ label-class-name="col-name-wrap"
show-overflow-tooltip
>
@@ -223,6 +232,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -289,6 +334,7 @@ import {
type PlanContractLink
} from '@/api/pl'
import PlPlanContractMultiSelect from '@/components/business/pl-plan-contract-multi-select/index.vue'
+import PlPlanListProductCell from '@/components/business/pl-plan-list-product-cell/index.vue'
import PlPlanViewDialog from '@/components/business/pl-plan-view-dialog/index.vue'
import ListQuickSearchInput from '@/components/business/list-quick-search-input/index.vue'
import ListToolbarDateShortcuts from '@/components/core/list-toolbar-date-shortcuts/index.vue'
@@ -363,6 +409,15 @@ function gapTag(row: PlanContractGapItem) {
return { text: '—', type: 'info' as const }
}
+function getPlanTypeLabel(type?: string | null) {
+ const map: Record = {
+ direct: '直销单',
+ inbound: '入库单',
+ outbound: '出库单'
+ }
+ return type ? map[type] || type : '—'
+}
+
function partySubText(row: PlanContractGapItem, side: 'supplier' | 'demander') {
if (side === 'supplier') {
if (row.supplierContractExempt) return '内置 · 合同可免'