feat(pl): 展示各装送段用证配置与独立占用明细并保留历史计量口径。
This commit is contained in:
1 parent
706260ff7a
commit
b764eb02a7
10 files changed
+497
-40
No files matched your search
@@ -29,3 +29,42 @@ for (const arrange of [false, true]) test(`stale ${arrange ? 'preview' : 'save'}
|
||||
assert.equal(h.state.visible.value, true)
|
||||
assert.equal(h.state.busy.value, true)
|
||||
})
|
||||
|
||||
for (const methodName of ['preview', 'loadContext'])
|
||||
test(`${methodName} ignores a result when configuration changes in flight`, async () => {
|
||||
const script = parse(
|
||||
readFileSync(
|
||||
new URL('../src/components/business/pl-plan-quota-dialog/index.vue', import.meta.url),
|
||||
'utf8'
|
||||
)
|
||||
).descriptor.scriptSetup!.content
|
||||
const ast = ts.createSourceFile('quota.ts', script, ts.ScriptTarget.Latest, true)
|
||||
const method = ast.statements
|
||||
.find((n) => ts.isFunctionDeclaration(n) && n.name?.text === methodName)!
|
||||
.getText(ast)
|
||||
let finish!: (value: any) => void
|
||||
const pending = new Promise<any>((r) => {
|
||||
finish = r
|
||||
})
|
||||
const state: any = {
|
||||
plan: { value: { planId: 'a' } },
|
||||
busy: { value: false },
|
||||
result: { value: undefined },
|
||||
context: { value: undefined },
|
||||
payload: () => ({}),
|
||||
previewVehicle: {},
|
||||
planQuotaApi: { preview: () => pending, context: () => pending }
|
||||
}
|
||||
const js = ts.transpileModule(
|
||||
`let epoch=0,revision=0;${method};return {run:${methodName},change:()=>revision++}`,
|
||||
{ compilerOptions: { target: ts.ScriptTarget.ES2022 } }
|
||||
).outputText
|
||||
const actions = new Function(...Object.keys(state), js)(...Object.values(state))
|
||||
const task = actions.run()
|
||||
actions.change()
|
||||
finish({ status: 'available', sourceFingerprint: 'old' })
|
||||
await task
|
||||
assert.equal(state.result.value, undefined)
|
||||
assert.equal(state.context.value, undefined)
|
||||
assert.equal(state.busy.value, false)
|
||||
})
|
||||
@@ -39,6 +39,7 @@ function pageHarness(kind: 'quota' | 'legal' | 'cert', props: any = { mobile: tr
|
||||
...Object.fromEntries(
|
||||
Object.entries(vue).filter(([key]) => /^[a-zA-Z_$][\w$]*$/.test(key) && key !== 'default')
|
||||
),
|
||||
useElementSize: () => ({ width: vue.ref(900) }),
|
||||
useListTableViewportHeight: () => ({ tableHeight: vue.ref(400) }),
|
||||
useClientColumnFilters,
|
||||
useServerColumnFilters,
|
||||
|
||||
@@ -38,6 +38,7 @@ function pageHarness(kind: 'quota' | 'legal', props: any = { mobile: true }) {
|
||||
...Object.fromEntries(
|
||||
Object.entries(vue).filter(([key]) => /^[a-zA-Z_$][\w$]*$/.test(key) && key !== 'default')
|
||||
),
|
||||
useElementSize: () => ({ width: vue.ref(900) }),
|
||||
useListTableViewportHeight: () => ({ tableHeight: vue.ref(400) }),
|
||||
useServerColumnFilters,
|
||||
usePersistedListColumns,
|
||||
@@ -47,6 +48,7 @@ function pageHarness(kind: 'quota' | 'legal', props: any = { mobile: true }) {
|
||||
onBeforeUnmount: (fn: Function) => unmount.push(fn),
|
||||
request: { get: (url: string, options: any) => queue(lists, { url, ...options.params }) },
|
||||
pickBxData: (value: any) => value,
|
||||
planQuotaApi: { get: (id: string) => queue(gets, id) },
|
||||
legalDocumentApi: {
|
||||
list: (params: any) => queue(lists, params),
|
||||
get: (id: string) => queue(gets, id),
|
||||
@@ -330,3 +332,21 @@ test('legal: read-only permission prevents writes even when detail contains atta
|
||||
assert.equal(h.writes.length, 0)
|
||||
h.stop()
|
||||
})
|
||||
|
||||
test('plan expansion loads on demand, preserves total during refresh and ignores previous tenant', async () => {
|
||||
const h = pageHarness('quota', { mobile: false }),
|
||||
s = h.state
|
||||
s.total.value = 45
|
||||
const refresh = s.load()
|
||||
assert.equal(s.total.value, 45)
|
||||
h.lists[0].resolve({ list: [{ id: 'plan' }], total: 45 })
|
||||
await refresh
|
||||
s.expand({ id: 'plan' }, [{ id: 'plan' }])
|
||||
assert.equal(h.gets.length, 1)
|
||||
assert.deepEqual(s.expandedIds.value, ['plan'])
|
||||
h.user.getUserInfo.tenantNo = 'other'
|
||||
h.gets[0].resolve({ planId: 'plan', sourceOptions: [], usages: [] })
|
||||
await flush()
|
||||
assert.deepEqual(Object.keys(s.details), [])
|
||||
h.stop()
|
||||
})
|
||||
@@ -0,0 +1,109 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
import {
|
||||
measuredSources,
|
||||
configurationPayload,
|
||||
sourceLabel,
|
||||
stopUsageRows
|
||||
} from '../src/components/business/pl-plan-quota-dialog/stop-model'
|
||||
const load: any = {
|
||||
sourceStopType: 'load',
|
||||
sourceStopProductId: 'a',
|
||||
planProductId: 'p',
|
||||
productName: '硫酸',
|
||||
stopOrder: 1,
|
||||
quantity: 100,
|
||||
unit: '吨',
|
||||
contractId: 'c1',
|
||||
contractProductId: 'cp1'
|
||||
}
|
||||
const delivery: any = {
|
||||
...load,
|
||||
sourceStopType: 'delivery',
|
||||
sourceStopProductId: 'b',
|
||||
quantity: 40,
|
||||
contractId: 'c2',
|
||||
contractProductId: 'cp2'
|
||||
}
|
||||
const plan: any = {
|
||||
measurementVersion: 'per_stop_v2',
|
||||
configuration: {},
|
||||
sourceOptions: [load, delivery],
|
||||
usages: []
|
||||
}
|
||||
test('v2 measures every stop and never sends legacy pairs; absent version stays legacy', () => {
|
||||
assert.deepEqual(measuredSources(plan, { p: 'load' }), [load, delivery])
|
||||
assert.equal(
|
||||
configurationPayload(plan, { sourceMapping: [{ old: true }] } as any, [load], {}).sourceMapping,
|
||||
null
|
||||
)
|
||||
assert.deepEqual(measuredSources({ ...plan, measurementVersion: undefined }, { p: 'load' }), [
|
||||
load
|
||||
])
|
||||
})
|
||||
test('source labels use stop and product instead of source UUID', () => {
|
||||
assert.equal(sourceLabel('delivery:b', plan.sourceOptions), '送货段1 · 硫酸')
|
||||
assert.equal(sourceLabel('load:unknown', []), '来源已不可用')
|
||||
})
|
||||
test('all stop products are visible and each certificate keeps its unit and quantity', () => {
|
||||
const data = {
|
||||
...plan,
|
||||
stopProducts: [
|
||||
{ ...load, isToxic: true },
|
||||
{ ...delivery, isToxic: true },
|
||||
{ ...load, sourceStopProductId: 'e', isToxic: false, isExplosive: true }
|
||||
],
|
||||
usages: [
|
||||
{
|
||||
certType: 'purchase',
|
||||
certNoSnapshot: 'BUY',
|
||||
quantityUnit: '吨',
|
||||
items: [
|
||||
{ sourceStopType: 'load', sourceStopProductId: 'a', reservedQty: '100', usedQty: '0' }
|
||||
]
|
||||
},
|
||||
{
|
||||
certType: 'transport',
|
||||
certNoSnapshot: 'MOVE',
|
||||
quantityUnit: '千克',
|
||||
items: [
|
||||
{ sourceStopType: 'load', sourceStopProductId: 'a', reservedQty: '100000', usedQty: '0' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
const rows = stopUsageRows(data)
|
||||
assert.equal(rows.length, 3)
|
||||
assert.equal(rows[0].purchase, 'BUY · 预占 100 吨 · 已用 0 吨')
|
||||
assert.equal(rows[0].transport, 'MOVE · 预占 100000 千克 · 已用 0 千克')
|
||||
assert.equal(rows[1].purchase, '缺配')
|
||||
assert.equal(rows[2].purchase, '无需两证')
|
||||
})
|
||||
test('draft bindings show zeros in their own units and hidden scope never claims missing', () => {
|
||||
const rows = stopUsageRows({
|
||||
...plan,
|
||||
stopProducts: [
|
||||
{
|
||||
...load,
|
||||
isToxic: true,
|
||||
bindingsReadable: true,
|
||||
certificates: [{ certType: 'purchase', certId: 'cert', certNo: 'BUY', quantityUnit: '桶' }]
|
||||
},
|
||||
{ ...delivery, isToxic: true, bindingsReadable: false }
|
||||
]
|
||||
})
|
||||
assert.equal(rows[0].purchase, 'BUY · 已绑定 · 预占 0 桶 · 已用 0 桶')
|
||||
assert.equal(rows[1].purchase, '合同或证件不可读取')
|
||||
})
|
||||
test('legacy counterpart is traceability only, never a second missing allocation', () => {
|
||||
const legacy = {
|
||||
...plan,
|
||||
measurementVersion: 'legacy_single_side',
|
||||
configuration: {
|
||||
sourceMapping: [
|
||||
{ sourceStopType: 'load', sourceStopProductId: 'a', relatedStopProductIds: ['b'] }
|
||||
]
|
||||
}
|
||||
}
|
||||
assert.equal(stopUsageRows(legacy as any)[1].purchase, '原口径:关联追溯,不另计量')
|
||||
})
|
||||
Reference in new issue
Block a user