feat(pl): show product and plan type columns on contract completion list
Align the合同完善 table with运输计划 by returning products/planType from contract-gaps and rendering them via PlPlanListProductCell. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
8e42fe1640
commit
09a57a7af9
4 files changed
+89
-2
No files matched your search
@@ -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}
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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 {
|
||||
|
||||
@@ -195,11 +195,18 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="planDate" label="计划日期" min-width="120" align="center" />
|
||||
<el-table-column prop="planType" label="单据类型" min-width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ getPlanTypeLabel(row.planType) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="supplierName"
|
||||
label="供方"
|
||||
min-width="200"
|
||||
align="left"
|
||||
class-name="col-name-wrap"
|
||||
label-class-name="col-name-wrap"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
@@ -214,6 +221,8 @@
|
||||
label="需方"
|
||||
min-width="200"
|
||||
align="left"
|
||||
class-name="col-name-wrap"
|
||||
label-class-name="col-name-wrap"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
@@ -223,6 +232,42 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="产品名称"
|
||||
min-width="240"
|
||||
align="left"
|
||||
class-name="col-name-wrap"
|
||||
label-class-name="col-name-wrap"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<PlPlanListProductCell :products="row.products" field="name" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="型号/规格" min-width="120" align="center" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<PlPlanListProductCell :products="row.products" field="model" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="包装规格" min-width="120" align="center" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<PlPlanListProductCell :products="row.products" field="packageSpec" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位" min-width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
<PlPlanListProductCell :products="row.products" field="unit" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="计划数量" min-width="100" align="right" header-align="right">
|
||||
<template #default="{ row }">
|
||||
<PlPlanListProductCell :products="row.products" field="planQty" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实际数量" min-width="100" align="right" header-align="right">
|
||||
<template #default="{ row }">
|
||||
<PlPlanListProductCell :products="row.products" field="actualQty" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="合同缺口" min-width="130" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="gapTag(row).type" size="small" effect="dark">
|
||||
@@ -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<string, string> = {
|
||||
direct: '直销单',
|
||||
inbound: '入库单',
|
||||
outbound: '出库单'
|
||||
}
|
||||
return type ? map[type] || type : '—'
|
||||
}
|
||||
|
||||
function partySubText(row: PlanContractGapItem, side: 'supplier' | 'demander') {
|
||||
if (side === 'supplier') {
|
||||
if (row.supplierContractExempt) return '内置 · 合同可免'
|
||||
|
||||
Reference in new issue
Block a user