fix(pl): 修复客户品类证件额度选证与回显问题。
优先消耗有到期日的证件,补齐品类产品选择与运输证适用品类回显,避免新规则安排与建档展示失败。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
b0c1bca683
commit
e8cda33a3c
10 files changed
+330
-44
No files matched your search
@@ -56,17 +56,45 @@ def get_category(db: Session, user, category_id: str, *, lock=False):
|
||||
.order_by(PlCertCategoryProduct.create_time.asc())
|
||||
.all()
|
||||
)
|
||||
names = _product_labels(db, user.tenant_no, [link.product_id for link in links])
|
||||
products = [
|
||||
{
|
||||
'id': link.id,
|
||||
'productId': link.product_id,
|
||||
'scopeStatus': link.scope_status,
|
||||
**names.get(link.product_id, {}),
|
||||
}
|
||||
for link in links
|
||||
]
|
||||
return _category_dict(row, products)
|
||||
|
||||
|
||||
def _product_labels(db: Session, tenant_no: str, product_ids):
|
||||
"""批量带出 CRM 产品编号/名称/型号,供品类编辑回显。"""
|
||||
ids = [pid for pid in dict.fromkeys(product_ids or []) if pid]
|
||||
if not ids:
|
||||
return {}
|
||||
from app.models.crm_models import CrmProduct
|
||||
|
||||
rows = (
|
||||
db.query(CrmProduct)
|
||||
.filter(
|
||||
CrmProduct.tenant_no == tenant_no,
|
||||
CrmProduct.del_flag == 0,
|
||||
CrmProduct.id.in_(ids),
|
||||
)
|
||||
.all()
|
||||
)
|
||||
return {
|
||||
row.id: {
|
||||
'productNo': row.product_no,
|
||||
'productName': row.product_name,
|
||||
'model': row.model,
|
||||
}
|
||||
for row in rows
|
||||
}
|
||||
|
||||
|
||||
def save_category(db: Session, user, data: dict, category_id=None):
|
||||
"""data: categoryName, categoryCode?, status?, remark?, products?
|
||||
products 遗漏则不改关联;显式 [] 清空(软删);带 id 就地改。
|
||||
|
||||
@@ -90,6 +90,7 @@ def select_whole_cover_certs(db, user, sources, *, vehicle_no: str, on_date: dat
|
||||
'产品未配置证用品类,请先在证用品类中关联系统产品后再安排运输',
|
||||
)
|
||||
|
||||
# MySQL ASC 会把 NULL 排最前;无到期证应排在末尾,优先消耗更早到期的证(D5)
|
||||
candidates = (
|
||||
db.query(PlPurchaseCert)
|
||||
.filter(
|
||||
@@ -99,7 +100,11 @@ def select_whole_cover_certs(db, user, sources, *, vehicle_no: str, on_date: dat
|
||||
PlPurchaseCert.customer_id == customer_id,
|
||||
PlPurchaseCert.category_id.in_(categories),
|
||||
)
|
||||
.order_by(PlPurchaseCert.valid_to.asc(), PlPurchaseCert.id.asc())
|
||||
.order_by(
|
||||
PlPurchaseCert.valid_to.is_(None).asc(),
|
||||
PlPurchaseCert.valid_to.asc(),
|
||||
PlPurchaseCert.id.asc(),
|
||||
)
|
||||
.all()
|
||||
)
|
||||
|
||||
@@ -150,7 +155,11 @@ def select_whole_cover_certs(db, user, sources, *, vehicle_no: str, on_date: dat
|
||||
PlTransportCert.customer_id == customer_id,
|
||||
PlTransportCert.id.in_(transport_cert_ids),
|
||||
)
|
||||
.order_by(PlTransportCert.valid_to.asc(), PlTransportCert.id.asc())
|
||||
.order_by(
|
||||
PlTransportCert.valid_to.is_(None).asc(),
|
||||
PlTransportCert.valid_to.asc(),
|
||||
PlTransportCert.id.asc(),
|
||||
)
|
||||
.all()
|
||||
)
|
||||
for transport in transports:
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
"""为客户+品类证件额度联调造数(测试库)。
|
||||
|
||||
造:客户、报价(accepted)、合同(executing)+ 合同产品、
|
||||
2 张购买证(10吨早到期 / 100吨晚到期)、2 张运输证(引用对应购买证)、期初 opening。
|
||||
|
||||
不造:品类↔系统产品关联(由用户在界面配置)。
|
||||
2 张购买证(10吨早到期 / 100吨晚到期)、2 张运输证(引用对应购买证)、期初 opening、
|
||||
品类↔种子产品关联(否则新规则选证会因「产品未配置证用品类」失败)。
|
||||
|
||||
用法:
|
||||
cd backend && source venv/bin/activate
|
||||
@@ -118,6 +117,7 @@ def main() -> int:
|
||||
print(f'category_id={category_id}')
|
||||
print(f'vehicle={VEHICLE}')
|
||||
print(f'purchase early 10 / late 100 ; transport pair each')
|
||||
ids['category_product'] = uid('cat-prod')
|
||||
|
||||
# cleanup previous seed with same ids
|
||||
cleanup_tables = [
|
||||
@@ -125,6 +125,7 @@ def main() -> int:
|
||||
('pl_transport_cert_purchase_ref', 'id', [ids['ref_early'], ids['ref_late']]),
|
||||
('pl_transport_cert', 'id', [ids['transport_early'], ids['transport_late']]),
|
||||
('pl_purchase_cert', 'id', [ids['purchase_early'], ids['purchase_late']]),
|
||||
('pl_cert_category_product', 'id', [ids['category_product']]),
|
||||
('crm_contract_product', 'id', [ids['contract_product']]),
|
||||
('crm_contract', 'id', [ids['contract']]),
|
||||
('crm_quote_product', 'id', [ids['quote_product']]),
|
||||
@@ -204,6 +205,18 @@ def main() -> int:
|
||||
)
|
||||
print('+ contract', contract_no, 'product_row', ids['contract_product'])
|
||||
|
||||
# 品类 ↔ 种子产品(选证按产品反查品类;缺关联则安排必败)
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT INTO pl_cert_category_product
|
||||
(id, category_id, product_id, scope_status,
|
||||
tenant_no, create_dept_id, create_by, create_time, update_time, del_flag)
|
||||
VALUES (%s,%s,%s,'active',%s,'',%s,%s,%s,0)
|
||||
""",
|
||||
(ids['category_product'], category_id, PRODUCT_ID, TENANT, OWNER_USER_ID, now, now),
|
||||
)
|
||||
print('+ category_product', PRODUCT_ID, '->', category_id)
|
||||
|
||||
def insert_purchase(pid, cert_no, total, valid_to, face='98% 工业硫酸'):
|
||||
cur.execute(
|
||||
"""
|
||||
|
||||
@@ -238,6 +238,84 @@ def test_legacy_waive_still_skips_settle(monkeypatch):
|
||||
assert result.get('waived') is True
|
||||
|
||||
|
||||
def test_null_valid_to_sorted_after_dated_certs():
|
||||
"""无到期日的证不能排在有到期日之前(MySQL NULL ASC 默认会错序)。"""
|
||||
from app.services import pl_quota_customer_select as sel
|
||||
from app.schemas.pl.quota_usage import UsageSource
|
||||
from app.models.pl_quota_models import PlPurchaseCert, PlCertQtyLog
|
||||
|
||||
source = UsageSource(
|
||||
source_stop_type='delivery',
|
||||
source_stop_id='s1',
|
||||
source_stop_product_id='sp1',
|
||||
plan_product_id='pp1',
|
||||
contract_id='c1',
|
||||
contract_product_id='cp1',
|
||||
product_id='prod1',
|
||||
quantity=Decimal('10'),
|
||||
unit='吨',
|
||||
fingerprint='z' * 64,
|
||||
snapshot={'customerId': 'cust1'},
|
||||
requires_transport=False,
|
||||
)
|
||||
unbounded = SimpleNamespace(
|
||||
id='buy-null', customer_id='cust1', category_id='cat1', status='active',
|
||||
verified_by='u', verified_time=datetime.now(), valid_from=None, valid_to=None,
|
||||
total_qty=Decimal('100'), used_qty=Decimal('0'), reserved_qty=Decimal('0'),
|
||||
quantity_unit='吨', opening_single_use_consumed=0,
|
||||
)
|
||||
dated = SimpleNamespace(
|
||||
id='buy-dated', customer_id='cust1', category_id='cat1', status='active',
|
||||
verified_by='u', verified_time=datetime.now(), valid_from=None, valid_to=date(2026, 12, 1),
|
||||
total_qty=Decimal('100'), used_qty=Decimal('0'), reserved_qty=Decimal('0'),
|
||||
quantity_unit='吨', opening_single_use_consumed=0,
|
||||
)
|
||||
|
||||
class PurchaseQuery:
|
||||
def __init__(self):
|
||||
self._order = None
|
||||
|
||||
def filter(self, *a, **k):
|
||||
return self
|
||||
|
||||
def order_by(self, *exprs):
|
||||
self._order = exprs
|
||||
return self
|
||||
|
||||
def all(self):
|
||||
# 应先按 valid_to IS NULL 升序(非空在前),再按 valid_to ASC
|
||||
assert self._order is not None and len(self._order) >= 2
|
||||
return [dated, unbounded]
|
||||
|
||||
class LogQuery:
|
||||
def filter_by(self, **k):
|
||||
return self
|
||||
|
||||
def first(self):
|
||||
return SimpleNamespace(id='opening')
|
||||
|
||||
db = MagicMock()
|
||||
|
||||
def q_side(model):
|
||||
if model is PlPurchaseCert:
|
||||
return PurchaseQuery()
|
||||
if model is PlCertQtyLog:
|
||||
return LogQuery()
|
||||
return MagicMock()
|
||||
|
||||
db.query.side_effect = q_side
|
||||
orig = sel.category_ids_for_product
|
||||
sel.category_ids_for_product = lambda db, tenant, pid: {'cat1'}
|
||||
try:
|
||||
purchase_ids, _, manuals = sel.select_whole_cover_certs(
|
||||
db, SimpleNamespace(tenant_no='t'), [source], vehicle_no='皖A1', on_date=date(2026, 11, 14),
|
||||
)
|
||||
finally:
|
||||
sel.category_ids_for_product = orig
|
||||
assert purchase_ids == ['buy-dated']
|
||||
assert manuals[0].permit_id == 'buy-dated'
|
||||
|
||||
|
||||
def test_same_city_source_skips_transport_requirement():
|
||||
"""同市来源 requires_transport=False,整笔选证可不带运输证。"""
|
||||
from app.services import pl_quota_customer_select as sel
|
||||
|
||||
@@ -428,7 +428,14 @@ export interface CertCategory {
|
||||
categoryName: string
|
||||
categoryCode?: string | null
|
||||
status: string
|
||||
products?: Array<{ id?: string; productId: string; scopeStatus?: string }>
|
||||
products?: Array<{
|
||||
id?: string
|
||||
productId: string
|
||||
scopeStatus?: string
|
||||
productNo?: string | null
|
||||
productName?: string | null
|
||||
model?: string | null
|
||||
}>
|
||||
}
|
||||
|
||||
export const certQuotaRuleApi = {
|
||||
|
||||
@@ -1606,7 +1606,7 @@
|
||||
message.includes('待完善') || message.includes('完善并关闭') || message.includes('证书齐备待完善')
|
||||
if (title !== '用证没有完成' || pendingFulfill) {
|
||||
const guide = pendingFulfill
|
||||
? `${html}<p class="pl-arrange-block-text">新规则下证书齐备只放行安排,不能绕过归档挂证。请到「证书齐备 → 待完善并关闭」上传证件并执行完善并关闭后再归档。</p>`
|
||||
? `${html}<p class="pl-arrange-block-text">新规则下证书齐备只放行安排,不能绕过归档挂证。请到「证书齐备 → 已标记待完善证书」上传证件并执行完善并关闭后再归档。</p>`
|
||||
: html
|
||||
await ElMessageBox.alert(guide, title, {
|
||||
type: 'warning',
|
||||
@@ -1618,7 +1618,7 @@
|
||||
return undefined
|
||||
}
|
||||
// 仅旧规则(legacy)允许在归档时标记证书齐备以跳过额度
|
||||
const readyHtml = `${html}<p class="pl-arrange-block-text">如果购买证、运输证还没上传,可以标记证书齐备。仅旧规则计划标记后这次归档不再校验、不扣这两证额度;新规则计划请走「待完善并关闭」。</p>`
|
||||
const readyHtml = `${html}<p class="pl-arrange-block-text">如果购买证、运输证还没上传,可以标记证书齐备。仅旧规则计划标记后这次归档不再校验、不扣这两证额度;新规则计划请走「证书齐备 → 已标记待完善证书」。</p>`
|
||||
try {
|
||||
await ElMessageBox.confirm(readyHtml, title, {
|
||||
type: 'warning',
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
<section class="pl-plan-form-section pl-plan-form-section--plain">
|
||||
<h3 class="pl-plan-form-section-title">关联购买证 / 运输证</h3>
|
||||
<p v-if="plan?.quotaCertPendingFulfill" class="pl-plan-form-empty-hint">
|
||||
证书齐备(待完善):请到「证书齐备 → 待完善并关闭」挂证
|
||||
证书齐备(待完善):请到「证书齐备 → 已标记待完善证书」挂证并完善并关闭
|
||||
</p>
|
||||
<div v-else-if="linkedCertRows.length" class="pl-plan-linked-certs">
|
||||
<div v-for="item in linkedCertRows" :key="item.key" class="pl-plan-linked-cert-row">
|
||||
|
||||
@@ -43,8 +43,12 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="openEdit(row)">编辑</el-button>
|
||||
<el-button link type="danger" @click="remove(row)">删除</el-button>
|
||||
<el-button link type="primary" @click="openEdit(row)">
|
||||
<el-icon><Edit /></el-icon>编辑
|
||||
</el-button>
|
||||
<el-button link type="danger" @click="remove(row)">
|
||||
<el-icon><Delete /></el-icon>删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</ListTable>
|
||||
@@ -55,9 +59,10 @@
|
||||
v-model="visible"
|
||||
class="dialog-form-layout"
|
||||
:title="editingId ? '编辑证用品类' : '新增证用品类'"
|
||||
width="640px"
|
||||
width="720px"
|
||||
align-center
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form label-position="top">
|
||||
<div class="form-grid-2">
|
||||
@@ -74,13 +79,41 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item label="关联系统产品(产品 ID,逗号分隔,保存后按差量写入)">
|
||||
<el-input
|
||||
v-model="productIdsText"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="例如:uuid1,uuid2(上线后可改产品选择器)"
|
||||
/>
|
||||
<el-form-item label="关联系统产品">
|
||||
<div class="cert-category-products">
|
||||
<div class="cert-category-products__toolbar">
|
||||
<span class="selected-hint">已选 {{ linkedProducts.length }} 项</span>
|
||||
<el-button type="primary" @click="productSelectorVisible = true">
|
||||
<el-icon><Plus /></el-icon>添加产品
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
v-if="linkedProducts.length"
|
||||
:data="linkedProducts"
|
||||
stripe
|
||||
class="list-table"
|
||||
row-key="productId"
|
||||
max-height="280"
|
||||
>
|
||||
<el-table-column prop="productNo" label="产品编号" min-width="120">
|
||||
<template #default="{ row }">{{ row.productNo || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="productName" label="产品名称" min-width="160">
|
||||
<template #default="{ row }">{{ row.productName || row.productId }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="model" label="规格型号" min-width="120">
|
||||
<template #default="{ row }">{{ row.model || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="100" fixed="right">
|
||||
<template #default="{ $index }">
|
||||
<el-button link type="danger" @click="removeLinked($index)">
|
||||
<el-icon><Delete /></el-icon>移除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<p v-else class="form-tip">暂未关联系统产品</p>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -92,13 +125,30 @@
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<ProductSelector
|
||||
:visible="productSelectorVisible"
|
||||
@update:visible="productSelectorVisible = $event"
|
||||
@confirm="onProductsConfirm"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { certCategoryApi, type CertCategory } from '@/api/pl/quota-cert'
|
||||
import { fetchProductDetail } from '@/api/crm/product'
|
||||
import ListQuickSearchInput from '@/components/business/list-quick-search-input/index.vue'
|
||||
import { Check, Close, Plus, Refresh } from '@element-plus/icons-vue'
|
||||
import ProductSelector from '@/components/business/product-selector/index.vue'
|
||||
import { Check, Close, Delete, Edit, Plus, Refresh } from '@element-plus/icons-vue'
|
||||
|
||||
type LinkedProduct = {
|
||||
id?: string
|
||||
productId: string
|
||||
productNo?: string | null
|
||||
productName?: string | null
|
||||
model?: string | null
|
||||
scopeStatus: 'active' | 'inactive'
|
||||
}
|
||||
|
||||
const keyword = ref('')
|
||||
const loading = ref(false)
|
||||
@@ -107,7 +157,8 @@
|
||||
const visible = ref(false)
|
||||
const editingId = ref('')
|
||||
const form = reactive({ categoryName: '', categoryCode: '', status: '1' })
|
||||
const productIdsText = ref('')
|
||||
const linkedProducts = ref<LinkedProduct[]>([])
|
||||
const productSelectorVisible = ref(false)
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
@@ -124,36 +175,93 @@
|
||||
void load()
|
||||
}
|
||||
|
||||
async function enrichMissingNames(items: LinkedProduct[]) {
|
||||
const missing = items.filter((item) => item.productId && !item.productName)
|
||||
if (!missing.length) return items
|
||||
await Promise.all(
|
||||
missing.map(async (item) => {
|
||||
try {
|
||||
const res = (await fetchProductDetail({ id: item.productId })) as Record<string, any>
|
||||
const data = res?.data || res
|
||||
item.productNo = data?.productNo || item.productNo
|
||||
item.productName = data?.productName || item.productName
|
||||
item.model = data?.model || item.model
|
||||
} catch {
|
||||
/* 保留 productId 回显 */
|
||||
}
|
||||
})
|
||||
)
|
||||
return items
|
||||
}
|
||||
|
||||
async function openEdit(row?: CertCategory) {
|
||||
editingId.value = row?.id || ''
|
||||
form.categoryName = row?.categoryName || ''
|
||||
form.categoryCode = row?.categoryCode || ''
|
||||
form.status = row?.status || '1'
|
||||
productIdsText.value = ''
|
||||
linkedProducts.value = []
|
||||
if (row?.id) {
|
||||
const detail = await certCategoryApi.get(row.id)
|
||||
productIdsText.value = (detail.products || [])
|
||||
const products = (detail.products || [])
|
||||
.filter((p) => p.scopeStatus !== 'inactive')
|
||||
.map((p) => p.productId)
|
||||
.join(',')
|
||||
.map((p) => ({
|
||||
id: p.id,
|
||||
productId: p.productId,
|
||||
productNo: p.productNo,
|
||||
productName: p.productName,
|
||||
model: p.model,
|
||||
scopeStatus: 'active' as const
|
||||
}))
|
||||
linkedProducts.value = await enrichMissingNames(products)
|
||||
}
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
function onProductsConfirm(products: Array<Record<string, any>>) {
|
||||
const existing = new Map(linkedProducts.value.map((item) => [item.productId, item]))
|
||||
let added = 0
|
||||
let skipped = 0
|
||||
for (const product of products || []) {
|
||||
const productId = String(product.id || '').trim()
|
||||
if (!productId) continue
|
||||
if (existing.has(productId)) {
|
||||
skipped += 1
|
||||
continue
|
||||
}
|
||||
const row: LinkedProduct = {
|
||||
productId,
|
||||
productNo: product.productNo || null,
|
||||
productName: product.productName || null,
|
||||
model: product.model || null,
|
||||
scopeStatus: 'active'
|
||||
}
|
||||
existing.set(productId, row)
|
||||
linkedProducts.value.push(row)
|
||||
added += 1
|
||||
}
|
||||
productSelectorVisible.value = false
|
||||
if (added) ElMessage.success(`已添加 ${added} 个产品`)
|
||||
else if (skipped) ElMessage.warning('所选产品均已关联')
|
||||
}
|
||||
|
||||
function removeLinked(index: number) {
|
||||
linkedProducts.value.splice(index, 1)
|
||||
}
|
||||
|
||||
async function save() {
|
||||
if (!form.categoryName.trim()) {
|
||||
ElMessage.warning('请填写品类名称')
|
||||
return
|
||||
}
|
||||
const productIds = productIdsText.value
|
||||
.split(/[,,\s]+/)
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
const body = {
|
||||
categoryName: form.categoryName.trim(),
|
||||
categoryCode: form.categoryCode || null,
|
||||
status: form.status,
|
||||
products: productIds.map((productId) => ({ productId, scopeStatus: 'active' }))
|
||||
products: linkedProducts.value.map((item) => ({
|
||||
...(item.id ? { id: item.id } : {}),
|
||||
productId: item.productId,
|
||||
scopeStatus: item.scopeStatus || 'active'
|
||||
}))
|
||||
}
|
||||
saving.value = true
|
||||
try {
|
||||
@@ -171,7 +279,9 @@
|
||||
|
||||
async function remove(row: CertCategory) {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确认删除品类「${row.categoryName}」?`, '删除', { type: 'warning' })
|
||||
await ElMessageBox.confirm(`确认删除品类「${row.categoryName}」?`, '删除', {
|
||||
type: 'warning'
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
@@ -188,3 +298,22 @@
|
||||
void load()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.cert-category-products {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cert-category-products__toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.selected-hint {
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
@@ -2,12 +2,12 @@
|
||||
<div class="page-container">
|
||||
<section class="panel">
|
||||
<div class="panel-top"><h2 class="title-bar-heading">证书齐备</h2></div>
|
||||
<el-tabs v-model="listMode" class="cert-ready-tabs" @tab-change="onModeChange">
|
||||
<el-tab-pane label="待标记齐备" name="mark" />
|
||||
<el-tab-pane label="已标记待完善证书" name="fulfill" />
|
||||
</el-tabs>
|
||||
<div class="list-toolbar">
|
||||
<div class="list-toolbar-row">
|
||||
<el-radio-group v-model="listMode" class="mr-3" @change="onModeChange">
|
||||
<el-radio-button value="mark">标记齐备</el-radio-button>
|
||||
<el-radio-button value="fulfill">待完善并关闭</el-radio-button>
|
||||
</el-radio-group>
|
||||
<ListQuickSearchInput
|
||||
v-model="keyword"
|
||||
placeholder="计划编号"
|
||||
@@ -117,9 +117,9 @@
|
||||
:loading="markingId === row.id"
|
||||
@click="mark(row)"
|
||||
>
|
||||
<el-icon><CircleCheck /></el-icon>证书齐备
|
||||
<el-icon><CircleCheck /></el-icon>标记齐备
|
||||
</el-button>
|
||||
<span v-else>已确认</span>
|
||||
<span v-else>已标记</span>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -284,6 +284,7 @@
|
||||
|
||||
function onModeChange() {
|
||||
page.value = 1
|
||||
selectedIds.value = []
|
||||
void load()
|
||||
}
|
||||
|
||||
@@ -296,9 +297,9 @@
|
||||
async function mark(row: CertReadyPlan) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'确认后可先安排运输;新规则下归档前仍须在「待完善并关闭」中挂上购买证/运输证。确认人和时间会记在本行。',
|
||||
'证书齐备',
|
||||
{ type: 'warning', confirmButtonText: '确认齐备', cancelButtonText: '取消' }
|
||||
'确认后可先安排运输;新规则下归档前仍须到「已标记待完善证书」页上传证件并执行完善并关闭。确认人和时间会记在本行。',
|
||||
'标记齐备',
|
||||
{ type: 'warning', confirmButtonText: '确认标记', cancelButtonText: '取消' }
|
||||
)
|
||||
} catch {
|
||||
return
|
||||
@@ -365,3 +366,18 @@
|
||||
void load()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.cert-ready-tabs {
|
||||
margin: 0 0 4px;
|
||||
padding: 0 24px;
|
||||
|
||||
:deep(> .el-tabs__header) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
:deep(> .el-tabs__content) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -99,8 +99,8 @@
|
||||
><el-icon><Check /></el-icon
|
||||
>{{ row.openingConfirmed ? '复核' : '期初确认' }}</el-button
|
||||
>
|
||||
<el-button size="small" @click="showContracts(row)"
|
||||
><el-icon><Connection /></el-icon>关联合同</el-button
|
||||
<el-button size="small" @click="view(row)"
|
||||
><el-icon><View /></el-icon>查看</el-button
|
||||
>
|
||||
</template></el-table-column
|
||||
>
|
||||
@@ -649,7 +649,7 @@
|
||||
|
||||
import QuotaHistoryDialog from './QuotaHistoryDialog.vue'
|
||||
const historyDialog = ref<InstanceType<typeof QuotaHistoryDialog>>()
|
||||
import { Check, Close, Connection, Delete, Document, Edit, Plus } from '@element-plus/icons-vue'
|
||||
import { Check, Close, Delete, Document, Edit, Plus, View } from '@element-plus/icons-vue'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { useDictStore } from '@/store/modules/dict'
|
||||
import { getProductUnitLabel } from '@/utils/crm-product-display'
|
||||
@@ -1283,6 +1283,10 @@
|
||||
function applyCertForm(targetKind: CertType, cert: QuotaCert) {
|
||||
const value = {
|
||||
...certPayload(targetKind, cert),
|
||||
// 运输证适用品类为只读展示字段,不进保存 payload
|
||||
...(targetKind === 'transport'
|
||||
? { derivedCategoryIds: [...(cert.derivedCategoryIds || [])] }
|
||||
: {}),
|
||||
contractBindings: (cert.contractBindings || []).map((binding) => ({
|
||||
...binding,
|
||||
products: binding.products.map((product) => ({ ...product }))
|
||||
@@ -1325,6 +1329,9 @@
|
||||
form.value = row
|
||||
? {
|
||||
...certPayload(targetKind, row),
|
||||
...(targetKind === 'transport'
|
||||
? { derivedCategoryIds: [...(row.derivedCategoryIds || [])] }
|
||||
: {}),
|
||||
totalQty: inputQty(row.totalQty),
|
||||
contractBindings: [],
|
||||
bindingsEditable: row.bindingsEditable !== false,
|
||||
@@ -1703,7 +1710,6 @@
|
||||
linkedContracts.value = links
|
||||
detailVisible.value = true
|
||||
}
|
||||
const showContracts = view
|
||||
async function refreshDetails() {
|
||||
if (detail.value && detailVisible.value) await view(detail.value)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user