Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6dc768efb | ||
|
|
96518c08fb | ||
|
|
54a34b24ba | ||
|
|
5dc695a138 | ||
|
|
406ed0ee6d |
No files matched your search
@@ -20,7 +20,13 @@
|
||||
v-for="option in displayOptions"
|
||||
:key="option.id"
|
||||
:value="option.id"
|
||||
:label="team ? option.teamName : `${option.projectName} · ${option.teamName}`"
|
||||
:label="
|
||||
team
|
||||
? option.teamName
|
||||
: teamId
|
||||
? option.projectName
|
||||
: `${option.projectName} · ${option.teamName}`
|
||||
"
|
||||
:disabled="!filter && option.disabled"
|
||||
/>
|
||||
</el-select>
|
||||
@@ -43,6 +49,8 @@
|
||||
const props = defineProps<{
|
||||
filter?: boolean
|
||||
team?: boolean
|
||||
/** When selecting projects, keep options under this team. */
|
||||
teamId?: string
|
||||
modelValue?: string
|
||||
bizType?: BizType
|
||||
disabled?: boolean
|
||||
@@ -54,15 +62,18 @@
|
||||
ready: [value: boolean]
|
||||
}>()
|
||||
const options = ref<ProjectOption[]>([])
|
||||
const displayOptions = computed(() =>
|
||||
props.team
|
||||
? [
|
||||
...new Map(
|
||||
options.value.map((option) => [option.teamId, { ...option, id: option.teamId }])
|
||||
).values()
|
||||
]
|
||||
const displayOptions = computed(() => {
|
||||
if (props.team) {
|
||||
return [
|
||||
...new Map(
|
||||
options.value.map((option) => [option.teamId, { ...option, id: option.teamId }])
|
||||
).values()
|
||||
]
|
||||
}
|
||||
return props.teamId
|
||||
? options.value.filter((option) => option.teamId === props.teamId)
|
||||
: options.value
|
||||
)
|
||||
})
|
||||
const loading = ref(false)
|
||||
const error = ref(false),
|
||||
retry = ref(0)
|
||||
|
||||
@@ -26,6 +26,8 @@ describe('flattenProtocolAuditRows', () => {
|
||||
id: 'p1',
|
||||
protocolNo: 'ZKZX1',
|
||||
motherName: '郭梦朵',
|
||||
projectName: '测试项目',
|
||||
teamName: '陈星项目组111',
|
||||
sampleCollectTime: '2025-09-15 08:51:00',
|
||||
sampleItems: [
|
||||
{ sampleTypeName: '脐带血', sampleCode: 'A1', labStorageCode: 'B0000004' },
|
||||
@@ -40,6 +42,25 @@ describe('flattenProtocolAuditRows', () => {
|
||||
assert.equal(rows[0].labStorageCode, 'B0000004')
|
||||
assert.equal(rows[1].labStorageCode, 'B0000004A')
|
||||
assert.equal(rows[0].protocolId, 'p1')
|
||||
assert.equal(rows[0].projectName, '测试项目')
|
||||
assert.equal(rows[0].teamName, '陈星项目组111')
|
||||
assert.equal(rows[1].projectName, '测试项目')
|
||||
assert.equal(rows[1].teamName, '陈星项目组111')
|
||||
})
|
||||
|
||||
it('copies protocol project attribution onto each flat row for table props', () => {
|
||||
const rows = flattenProtocolAuditRows([
|
||||
{
|
||||
id: 'p-attr',
|
||||
projectName: '归属项目',
|
||||
teamName: '归属项目组',
|
||||
sampleItems: [{ sampleCode: 'ZKZX34000199', sampleTypeName: '围产期造血干细胞' }]
|
||||
}
|
||||
])
|
||||
assert.equal(rows.length, 1)
|
||||
assert.equal(rows[0].projectName, '归属项目')
|
||||
assert.equal(rows[0].teamName, '归属项目组')
|
||||
assert.equal(rows[0].sampleCode, 'ZKZX34000199')
|
||||
})
|
||||
|
||||
it('zero samples → one placeholder row, not co-stored', () => {
|
||||
|
||||
@@ -20,6 +20,9 @@ export type ProtocolAuditFlatRow = {
|
||||
sampleCount: number
|
||||
sampleIndex: number
|
||||
isCoStored: boolean
|
||||
/** 协议级项目归属,供扁平行表格 prop 绑定 */
|
||||
projectName: string
|
||||
teamName: string
|
||||
collectDate: string
|
||||
sampleCollectTime: string
|
||||
motherName: string
|
||||
@@ -95,6 +98,8 @@ export function flattenProtocolAuditRows(
|
||||
sampleCount,
|
||||
sampleIndex,
|
||||
isCoStored,
|
||||
projectName: str(protocol.projectName),
|
||||
teamName: str(protocol.teamName),
|
||||
collectDate: extractCollectDate(str(protocol.sampleCollectTime)),
|
||||
sampleCollectTime: str(protocol.sampleCollectTime),
|
||||
motherName: str(protocol.motherName),
|
||||
|
||||
@@ -143,57 +143,58 @@
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="项目归属维护"
|
||||
width="720px"
|
||||
width="560px"
|
||||
class="dialog-form-layout"
|
||||
append-to-body
|
||||
modal-class="protocol-dialog-mask"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form label-position="top"
|
||||
><el-form-item label="目标项目" required
|
||||
><ProjectSelect
|
||||
v-model="target"
|
||||
:key="selected.id"
|
||||
<el-form class="form-grid-2" label-position="top">
|
||||
<el-form-item label="原所属项目组">
|
||||
<el-input :model-value="selected.teamName || '未归属'" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="原所属项目">
|
||||
<el-input :model-value="selected.projectName || '未归属'" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="新项目组" required>
|
||||
<ProjectSelect
|
||||
:key="`team-${selected.id}`"
|
||||
team
|
||||
v-model="targetTeamId"
|
||||
:biz-type="selected.bizType"
|
||||
@update:model-value="previewRows = []" /></el-form-item
|
||||
><el-form-item label="调整原因" required
|
||||
><el-input v-model="reason" type="textarea" :maxlength="500" /></el-form-item
|
||||
></el-form>
|
||||
<el-alert
|
||||
title="请先预览全部关联单据,再确认调整。关联集合或归属发生变化时需要重新预览。"
|
||||
type="info"
|
||||
:closable="false"
|
||||
/>
|
||||
<el-table :data="previewRows" class="list-table" stripe
|
||||
><el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center" />
|
||||
<el-table-column prop="bizNo" label="关联单据" min-width="200" /><el-table-column
|
||||
prop="projectName"
|
||||
label="原项目"
|
||||
min-width="160"
|
||||
/></el-table>
|
||||
<template #footer
|
||||
><el-button @click="visible = false"
|
||||
@update:model-value="onTargetTeamChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="新项目" required>
|
||||
<ProjectSelect
|
||||
:key="`project-${selected.id}-${targetTeamId}`"
|
||||
v-model="target"
|
||||
:team-id="targetTeamId"
|
||||
:biz-type="selected.bizType"
|
||||
:disabled="!targetTeamId"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="调整原因" required class="form-col-span-2">
|
||||
<el-input v-model="reason" type="textarea" :maxlength="500" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="visible = false"
|
||||
><el-icon><Close /></el-icon>取消</el-button
|
||||
><el-button :loading="busy" @click="preview"
|
||||
><el-icon><View /></el-icon>预览关联单据</el-button
|
||||
><el-button
|
||||
>
|
||||
<el-button
|
||||
type="primary"
|
||||
:disabled="!previewRows.length || !reason.trim()"
|
||||
:disabled="!target || !reason.trim()"
|
||||
:loading="busy"
|
||||
@click="commit"
|
||||
><el-icon><Check /></el-icon>确认调整</el-button
|
||||
></template
|
||||
>
|
||||
>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog
|
||||
v-model="historyVisible"
|
||||
title="归属审计记录"
|
||||
width="800px"
|
||||
width="1100px"
|
||||
append-to-body
|
||||
class="dialog-form-layout"
|
||||
><el-table :data="historyRows" class="list-table" stripe
|
||||
@@ -233,7 +234,7 @@
|
||||
presignOrderStatusTagType,
|
||||
applicationStatusTagType
|
||||
} from '@/utils/status-tag-registry'
|
||||
import { Search, Edit, Clock, Close, View, Check, RefreshRight } from '@element-plus/icons-vue'
|
||||
import { Search, Edit, Clock, Close, Check, RefreshRight } from '@element-plus/icons-vue'
|
||||
import { assignmentTabs, assignmentColumns, assignmentKeywordHints } from './assignment-columns'
|
||||
import ProjectSelect from '@/components/business/ProjectSelect.vue'
|
||||
import { assignmentSnapshot } from '@/utils/project-management'
|
||||
@@ -268,10 +269,10 @@
|
||||
loadError = ref(false),
|
||||
visible = ref(false),
|
||||
busy = ref(false),
|
||||
targetTeamId = ref(''),
|
||||
target = ref(''),
|
||||
reason = ref(''),
|
||||
selected = ref<ProjectRecord>({}),
|
||||
previewRows = ref<ProjectRecord[]>([]),
|
||||
historyVisible = ref(false),
|
||||
historyRows = ref<ProjectRecord[]>([])
|
||||
const visibleColumns = computed(() =>
|
||||
@@ -361,47 +362,54 @@
|
||||
}
|
||||
function open(row: ProjectRecord) {
|
||||
selected.value = row
|
||||
targetTeamId.value = ''
|
||||
target.value = ''
|
||||
reason.value = ''
|
||||
previewRows.value = []
|
||||
visible.value = true
|
||||
}
|
||||
async function preview() {
|
||||
function onTargetTeamChange() {
|
||||
target.value = ''
|
||||
}
|
||||
async function commit() {
|
||||
if (!targetTeamId.value) {
|
||||
ElMessage.warning('请选择新项目组')
|
||||
return
|
||||
}
|
||||
if (!target.value) {
|
||||
ElMessage.warning('请选择目标项目')
|
||||
ElMessage.warning('请选择新项目')
|
||||
return
|
||||
}
|
||||
if (!reason.value.trim()) {
|
||||
ElMessage.warning('请填写调整原因')
|
||||
return
|
||||
}
|
||||
busy.value = true
|
||||
const previewTarget = target.value
|
||||
const previewRecord = selected.value
|
||||
const commitTarget = target.value
|
||||
const commitRecord = selected.value
|
||||
try {
|
||||
const result = await previewAssignment({
|
||||
const preview = await previewAssignment({
|
||||
bizType: selected.value.bizType,
|
||||
id: selected.value.id,
|
||||
projectId: target.value
|
||||
})
|
||||
if (selected.value === previewRecord && target.value === previewTarget && visible.value) {
|
||||
previewRows.value = result.records
|
||||
if (selected.value !== commitRecord || target.value !== commitTarget || !visible.value) {
|
||||
return
|
||||
}
|
||||
} finally {
|
||||
busy.value = false
|
||||
}
|
||||
}
|
||||
async function commit() {
|
||||
busy.value = true
|
||||
try {
|
||||
await saveAssignment({
|
||||
bizType: selected.value.bizType,
|
||||
id: selected.value.id,
|
||||
projectId: target.value,
|
||||
reason: reason.value.trim(),
|
||||
expectedAssignments: assignmentSnapshot(previewRows.value)
|
||||
expectedAssignments: assignmentSnapshot(preview.records)
|
||||
})
|
||||
if (selected.value !== commitRecord || target.value !== commitTarget || !visible.value) {
|
||||
return
|
||||
}
|
||||
visible.value = false
|
||||
ElMessage.success('归属已调整')
|
||||
await load()
|
||||
} catch {
|
||||
previewRows.value = []
|
||||
// API layer already surfaces the conflict / validation message.
|
||||
} finally {
|
||||
busy.value = false
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user