- 销售部门选择改为 el-tree-select 树形组件,展示组织架构层级 - 物料选择弹窗过滤停用产品(enableFlag: 'Y') - 物料弹窗单行选择模式改用 el-radio 单选框,选中状态更明显 - 物料编码列宽度由 120px 改为 min-width 160px,显示完整编码 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1335 lines
42 KiB
Vue
1335 lines
42 KiB
Vue
<template>
|
||
<div class="form-container">
|
||
<!-- 顶部操作栏 -->
|
||
<div class="page-header">
|
||
<div class="header-left">
|
||
<el-button @click="handleBack" :icon="ArrowLeft">返回</el-button>
|
||
<span class="page-title">{{ pageTitle }}</span>
|
||
</div>
|
||
<div class="header-right">
|
||
<template v-if="!isView">
|
||
<el-button type="primary" @click="handleSave" :loading="saveLoading">
|
||
<el-icon><Check /></el-icon>保存
|
||
</el-button>
|
||
<el-button v-if="isEdit && formData.orderStatus === '开立'" @click="handleWithdraw">
|
||
撤回
|
||
</el-button>
|
||
</template>
|
||
<el-button
|
||
v-if="canAudit"
|
||
type="success"
|
||
@click="handleAudit"
|
||
>
|
||
<el-icon><Finished /></el-icon>审核
|
||
</el-button>
|
||
<el-button
|
||
v-if="canUnaudit"
|
||
type="warning"
|
||
@click="handleUnaudit"
|
||
>
|
||
反审核
|
||
</el-button>
|
||
<el-dropdown v-if="isView || isEdit" trigger="click">
|
||
<el-button>
|
||
操作<el-icon class="el-icon--right"><ArrowDown /></el-icon>
|
||
</el-button>
|
||
<template #dropdown>
|
||
<el-dropdown-menu>
|
||
<el-dropdown-item @click="handlePrint">打印</el-dropdown-item>
|
||
<el-dropdown-item v-if="isView" @click="handleEdit">编辑</el-dropdown-item>
|
||
<el-dropdown-item v-if="isView" @click="handleAdd">新增</el-dropdown-item>
|
||
</el-dropdown-menu>
|
||
</template>
|
||
</el-dropdown>
|
||
<el-button @click="toggleCollapse">
|
||
{{ isCollapsed ? '展开' : '收起' }}
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<el-card shadow="never" class="main-card">
|
||
<el-form
|
||
ref="formRef"
|
||
:model="formData"
|
||
:rules="formRules"
|
||
:disabled="isView"
|
||
label-width="100px"
|
||
>
|
||
<!-- 基础信息区域 -->
|
||
<el-collapse v-model="activeCollapse">
|
||
<el-collapse-item title="基础信息" name="basic">
|
||
<!-- 第一行:单据信息 -->
|
||
<el-row :gutter="16">
|
||
<el-col :span="6">
|
||
<el-form-item label="单据编码">
|
||
<el-input v-model="formData.orderCode" disabled placeholder="系统自动生成" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="单据日期" prop="orderDate">
|
||
<el-date-picker
|
||
v-model="formData.orderDate"
|
||
type="date"
|
||
placeholder="选择日期"
|
||
value-format="YYYY-MM-DD"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="单据状态">
|
||
<el-tag :type="getStatusType(formData.orderStatus)">
|
||
{{ getStatusLabel(formData.orderStatus) }}
|
||
</el-tag>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="单据类型">
|
||
<el-input value="销售订单" disabled />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<!-- 第二行:业务信息 -->
|
||
<el-row :gutter="16">
|
||
<el-col :span="6">
|
||
<el-form-item label="业务类型">
|
||
<el-select v-model="formData.bizType" disabled style="width: 100%">
|
||
<el-option
|
||
v-for="item in bizTypeOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="业务状态">
|
||
<el-select v-model="formData.bizStatus" style="width: 100%">
|
||
<el-option
|
||
v-for="item in bizStatusOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="销售部门" prop="deptId">
|
||
<el-tree-select
|
||
v-model="formData.deptId"
|
||
:data="deptOptions"
|
||
:props="{ value: 'deptId', label: 'deptName', children: 'children' }"
|
||
placeholder="请选择销售部门"
|
||
clearable
|
||
check-strictly
|
||
filterable
|
||
style="width: 100%"
|
||
@change="onDeptChange"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="销售人员" prop="salesmanId">
|
||
<el-select
|
||
v-model="formData.salesmanId"
|
||
placeholder="请选择销售人员"
|
||
clearable
|
||
filterable
|
||
style="width: 100%"
|
||
@change="onSalesmanChange"
|
||
>
|
||
<el-option
|
||
v-for="item in salesmanOptions"
|
||
:key="item.userId"
|
||
:label="item.userName || item.nickName || ''"
|
||
:value="item.userId"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<!-- 第三行:操作人员 -->
|
||
<el-row :gutter="16">
|
||
<el-col :span="6">
|
||
<el-form-item label="操作员">
|
||
<el-input v-model="formData.operatorName" disabled />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="审核员">
|
||
<el-input v-model="formData.auditorName" disabled />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="审核日期">
|
||
<el-input v-model="formData.auditDate" disabled />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="销售类型" prop="salesType">
|
||
<el-select v-model="formData.salesType" placeholder="请选择" style="width: 100%">
|
||
<el-option
|
||
v-for="item in salesTypeOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<!-- 第四行:供货方式 -->
|
||
<el-row :gutter="16">
|
||
<el-col :span="6">
|
||
<el-form-item label="供货方式" prop="supplyMode">
|
||
<el-select v-model="formData.supplyMode" placeholder="请选择" style="width: 100%">
|
||
<el-option
|
||
v-for="item in supplyModeOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<!-- 第五行:客户信息 -->
|
||
<el-row :gutter="16">
|
||
<el-col :span="6">
|
||
<el-form-item label="客户名称" prop="clientId">
|
||
<el-input
|
||
v-model="formData.clientName"
|
||
placeholder="点击选择客户"
|
||
:readonly="!isView"
|
||
@click="!isView && (showCustomerDialog = true)"
|
||
>
|
||
<template #append v-if="!isView">
|
||
<el-button @click="showCustomerDialog = true">选择</el-button>
|
||
</template>
|
||
</el-input>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="合同号">
|
||
<el-input v-model="formData.contractNo" placeholder="请输入合同号">
|
||
<template #append v-if="!isView">
|
||
<el-button @click="handleUploadContract">上传</el-button>
|
||
</template>
|
||
</el-input>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="付款条件">
|
||
<el-input v-model="formData.paymentTerms" placeholder="如:款到发货" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="交付日期" prop="deliveryDate">
|
||
<el-date-picker
|
||
v-model="formData.deliveryDate"
|
||
type="date"
|
||
placeholder="选择日期"
|
||
value-format="YYYY-MM-DD"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<!-- 第六行:交期/收货信息 -->
|
||
<el-row :gutter="16">
|
||
<el-col :span="6">
|
||
<el-form-item label="交期状态" prop="deliveryStatus">
|
||
<el-radio-group v-model="formData.deliveryStatus">
|
||
<el-radio value="estimate">预计</el-radio>
|
||
<el-radio value="confirm">确认</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="收货人">
|
||
<el-input v-model="formData.receiver" placeholder="请输入收货人" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="电话">
|
||
<el-input v-model="formData.receiverPhone" placeholder="请输入电话" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="目的国家">
|
||
<el-input v-model="formData.destCountry" placeholder="请输入目的国家" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<!-- 第七行:地址/备注 -->
|
||
<el-row :gutter="16">
|
||
<el-col :span="12">
|
||
<el-form-item label="地址">
|
||
<el-input v-model="formData.receiverAddress" placeholder="请输入收货地址" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="备注信息">
|
||
<el-input v-model="formData.remark" placeholder="请输入备注" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
</el-collapse-item>
|
||
</el-collapse>
|
||
|
||
<!-- 物料明细区域 -->
|
||
<div class="material-section">
|
||
<div class="section-header">
|
||
<span class="section-title">物料信息</span>
|
||
<div class="section-toolbar" v-if="!isView">
|
||
<el-button
|
||
type="primary"
|
||
size="small"
|
||
@click="handleImport"
|
||
:disabled="!formData.clientId"
|
||
>
|
||
引入
|
||
</el-button>
|
||
<el-button
|
||
type="primary"
|
||
size="small"
|
||
@click="handleBatchAddMaterial"
|
||
:disabled="!formData.clientId"
|
||
>
|
||
<el-icon><Plus /></el-icon>新增物料
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<el-table
|
||
:data="formData.lines"
|
||
stripe
|
||
border
|
||
style="width: 100%"
|
||
max-height="400"
|
||
empty-text="暂无物料明细,请点击上方按钮添加物料"
|
||
>
|
||
<el-table-column type="index" label="序号" width="60" />
|
||
<el-table-column prop="itemCode" label="物料编码" width="130">
|
||
<template #default="{ row }">
|
||
<template v-if="!isView">
|
||
<el-input
|
||
v-model="row.itemCode"
|
||
placeholder="选择物料"
|
||
readonly
|
||
@click="selectMaterialForRow(row)"
|
||
@focus="selectMaterialForRow(row)"
|
||
>
|
||
<template #append>
|
||
<el-button size="small" @click.stop="selectMaterialForRow(row)">选择</el-button>
|
||
</template>
|
||
</el-input>
|
||
</template>
|
||
<span v-else>{{ row.itemCode }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="itemName" label="物料名称" min-width="180" show-overflow-tooltip />
|
||
<el-table-column prop="specification" label="型号规格" width="150" show-overflow-tooltip />
|
||
<el-table-column prop="unitOfMeasure" label="主计量" width="80" />
|
||
<el-table-column prop="quantity" label="数量" width="120">
|
||
<template #default="{ row }">
|
||
<el-input-number
|
||
v-if="!isView"
|
||
v-model="row.quantity"
|
||
:min="0"
|
||
:precision="2"
|
||
:controls="false"
|
||
size="small"
|
||
placeholder="请输入"
|
||
style="width: 100%"
|
||
@change="calculateLineAmount(row)"
|
||
@blur="calculateLineAmount(row)"
|
||
/>
|
||
<span v-else>{{ formatNumber(row.quantity) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="unitPrice" label="单价" width="120">
|
||
<template #default="{ row }">
|
||
<el-input-number
|
||
v-if="!isView"
|
||
v-model="row.unitPrice"
|
||
:min="0"
|
||
:precision="2"
|
||
:controls="false"
|
||
size="small"
|
||
placeholder="请输入"
|
||
style="width: 100%"
|
||
@change="calculateLineAmount(row)"
|
||
@blur="calculateLineAmount(row)"
|
||
/>
|
||
<span v-else>{{ formatCurrency(row.unitPrice) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="amount" label="金额" width="120" align="right">
|
||
<template #default="{ row }">
|
||
{{ formatCurrency(row.amount) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="qualityReq" label="质量要求" width="150">
|
||
<template #default="{ row }">
|
||
<el-input
|
||
v-if="!isView"
|
||
v-model="row.qualityReq"
|
||
size="small"
|
||
placeholder="质量要求"
|
||
/>
|
||
<span v-else>{{ row.qualityReq || '-' }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="remark" label="备注" min-width="150">
|
||
<template #default="{ row }">
|
||
<el-input
|
||
v-if="!isView"
|
||
v-model="row.remark"
|
||
size="small"
|
||
placeholder="备注"
|
||
/>
|
||
<span v-else>{{ row.remark || '-' }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column v-if="!isView" label="操作" width="80" fixed="right">
|
||
<template #default="{ $index }">
|
||
<el-button type="danger" link @click="handleDeleteLine($index)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<!-- 合计区域 -->
|
||
<div class="summary-row">
|
||
<span>合计金额:</span>
|
||
<span class="total-amount">{{ formatCurrency(totalAmount) }}</span>
|
||
</div>
|
||
</div>
|
||
</el-form>
|
||
</el-card>
|
||
|
||
<!-- 客户选择弹窗 -->
|
||
<el-dialog v-model="showCustomerDialog" title="选择客户" width="800px" destroy-on-close>
|
||
<div class="dialog-search">
|
||
<el-input
|
||
v-model="customerSearch"
|
||
placeholder="客户名称/联系人"
|
||
clearable
|
||
style="width: 300px"
|
||
@keyup.enter="loadCustomers"
|
||
>
|
||
<template #append>
|
||
<el-button :icon="Search" @click="loadCustomers" />
|
||
</template>
|
||
</el-input>
|
||
</div>
|
||
<el-table
|
||
v-loading="customerLoading"
|
||
:data="customerList"
|
||
stripe
|
||
border
|
||
highlight-current-row
|
||
@row-click="handleSelectCustomer"
|
||
max-height="400"
|
||
>
|
||
<el-table-column type="index" label="序号" width="60" />
|
||
<el-table-column prop="clientName" label="客户名称" min-width="180" />
|
||
<el-table-column prop="contact1" label="联系人" width="100" />
|
||
<el-table-column prop="contact1Tel" label="手机号" width="130" />
|
||
<el-table-column prop="enableFlag" label="状态" width="80" align="center">
|
||
<template #default="{ row }">
|
||
<el-tag :type="row.enableFlag === 'Y' ? 'success' : 'info'">
|
||
{{ row.enableFlag === 'Y' ? '正常' : '停用' }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="80">
|
||
<template #default="{ row }">
|
||
<el-button type="primary" link @click.stop="handleSelectCustomer(row)">选择</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</el-dialog>
|
||
|
||
<!-- 打印对话框 -->
|
||
<PrintDialog
|
||
v-model:visible="showPrintDialog"
|
||
:config="printConfig"
|
||
/>
|
||
|
||
<!-- 物料选择弹窗 -->
|
||
<el-dialog
|
||
v-model="showMaterialDialog"
|
||
:title="currentEditingRow ? '选择物料(单行)' : '选择物料(可多选)'"
|
||
width="900px"
|
||
destroy-on-close
|
||
>
|
||
<div class="dialog-search">
|
||
<el-input
|
||
v-model="materialSearch"
|
||
placeholder="物料编码/名称"
|
||
clearable
|
||
style="width: 300px"
|
||
@keyup.enter="loadMaterials"
|
||
>
|
||
<template #append>
|
||
<el-button :icon="Search" @click="loadMaterials" />
|
||
</template>
|
||
</el-input>
|
||
</div>
|
||
<el-table
|
||
ref="materialTableRef"
|
||
v-loading="materialLoading"
|
||
:data="materialList"
|
||
stripe
|
||
border
|
||
:row-key="(row: Material) => row.id"
|
||
highlight-current-row
|
||
@selection-change="handleMaterialSelectionChange"
|
||
@row-click="handleMaterialRowClick"
|
||
max-height="400"
|
||
>
|
||
<el-table-column
|
||
v-if="!currentEditingRow"
|
||
type="selection"
|
||
width="55"
|
||
:reserve-selection="true"
|
||
/>
|
||
<el-table-column
|
||
v-else
|
||
width="55"
|
||
align="center"
|
||
>
|
||
<template #default="{ row }">
|
||
<el-radio
|
||
v-model="selectedRadioId"
|
||
:value="row.id"
|
||
@change="handleRadioSelect(row)"
|
||
/>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="code" label="物料编码" min-width="160" show-overflow-tooltip />
|
||
<el-table-column prop="name" label="物料名称" min-width="180" show-overflow-tooltip />
|
||
<el-table-column prop="spec" label="规格型号" width="150" show-overflow-tooltip />
|
||
<el-table-column prop="unit" label="单位" width="80" />
|
||
<el-table-column prop="category" label="分类" width="120" />
|
||
<el-table-column label="状态" width="80" align="center">
|
||
<template #default="{ row }">
|
||
<el-tag :type="row.status === 1 ? 'success' : 'info'" size="small">
|
||
{{ row.status === 1 ? '启用' : '停用' }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<template #footer>
|
||
<div style="text-align: right">
|
||
<el-button @click="handleCancelMaterialDialog">取消</el-button>
|
||
<el-button
|
||
type="primary"
|
||
@click="handleConfirmMaterials"
|
||
:disabled="selectedMaterials.length === 0"
|
||
>
|
||
确定
|
||
</el-button>
|
||
</div>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, reactive, computed, onMounted, watch, nextTick } from 'vue'
|
||
import { useRoute, useRouter } from 'vue-router'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
import {
|
||
Search, Plus, Check, Finished, ArrowLeft, ArrowDown
|
||
} from '@element-plus/icons-vue'
|
||
import {
|
||
getSalesOrderDetail,
|
||
createSalesOrder,
|
||
updateSalesOrder,
|
||
auditSalesOrder,
|
||
unauditSalesOrder,
|
||
ORDER_STATUS_MAP,
|
||
SALES_TYPE_OPTIONS,
|
||
SUPPLY_MODE_OPTIONS,
|
||
BIZ_TYPE_OPTIONS,
|
||
BIZ_STATUS_OPTIONS,
|
||
type SalesOrder,
|
||
type SalesOrderLine
|
||
} from '@/api/salesOrder'
|
||
import PrintDialog from '@/components/print/PrintDialog.vue'
|
||
import type { PrintConfig } from '@/components/print/types'
|
||
import { getCustomerList, type Customer } from '@/api/customer'
|
||
import { getMaterialList, type Material } from '@/api/material'
|
||
import { listDept, handleTree, type Dept } from '@/api/system/dept'
|
||
import { listUser, type User } from '@/api/system/user'
|
||
|
||
const route = useRoute()
|
||
const router = useRouter()
|
||
|
||
// ============ 响应式状态 ============
|
||
|
||
/** 页面模式 */
|
||
const isAdd = computed(() => !route.params.id)
|
||
const isEdit = computed(() => !!route.params.id && route.path.includes('/form/'))
|
||
const isView = computed(() => route.path.includes('/view/'))
|
||
|
||
/** 页面标题 */
|
||
const pageTitle = computed(() => {
|
||
if (isAdd.value) return '新增销售订单'
|
||
if (isEdit.value) return '编辑销售订单'
|
||
return '销售订单详情'
|
||
})
|
||
|
||
/** 保存加载状态 */
|
||
const saveLoading = ref(false)
|
||
|
||
/** 折叠状态 */
|
||
const isCollapsed = ref(false)
|
||
const activeCollapse = ref(['basic'])
|
||
|
||
/** 表单引用 */
|
||
const formRef = ref()
|
||
|
||
/** 表单数据 */
|
||
const formData = reactive<SalesOrder>({
|
||
orderId: undefined,
|
||
orderCode: '',
|
||
orderDate: new Date().toISOString().split('T')[0],
|
||
orderStatus: '开立',
|
||
bizType: 'sales_order',
|
||
bizStatus: 'normal',
|
||
salesType: 'factory',
|
||
supplyMode: 'plan',
|
||
deliveryStatus: 'estimate',
|
||
clientId: undefined,
|
||
clientCode: '',
|
||
clientName: '',
|
||
contractNo: '',
|
||
paymentTerms: '',
|
||
deliveryDate: '',
|
||
receiver: '',
|
||
receiverPhone: '',
|
||
receiverAddress: '',
|
||
destCountry: '',
|
||
deptId: undefined,
|
||
deptName: '',
|
||
salesmanId: undefined,
|
||
salesmanName: '',
|
||
operatorId: undefined,
|
||
operatorName: '',
|
||
auditorId: undefined,
|
||
auditorName: '',
|
||
auditDate: '',
|
||
totalAmount: 0,
|
||
currency: 'CNY',
|
||
remark: '',
|
||
lines: []
|
||
})
|
||
|
||
/** 表单验证规则 */
|
||
const formRules = {
|
||
orderDate: [{ required: true, message: '请选择订单日期', trigger: 'change' }],
|
||
clientId: [{ required: true, message: '请选择客户', trigger: 'change' }],
|
||
deliveryDate: [{ required: true, message: '请选择交付日期', trigger: 'change' }],
|
||
salesType: [{ required: true, message: '请选择销售类型', trigger: 'change' }],
|
||
supplyMode: [{ required: true, message: '请选择供货方式', trigger: 'change' }],
|
||
deliveryStatus: [{ required: true, message: '请选择交期状态', trigger: 'change' }]
|
||
}
|
||
|
||
/** 选项数据 */
|
||
const salesTypeOptions = SALES_TYPE_OPTIONS
|
||
const supplyModeOptions = SUPPLY_MODE_OPTIONS
|
||
const bizTypeOptions = BIZ_TYPE_OPTIONS
|
||
const bizStatusOptions = BIZ_STATUS_OPTIONS
|
||
|
||
/** 客户选择 */
|
||
const showCustomerDialog = ref(false)
|
||
const customerSearch = ref('')
|
||
const customerList = ref<Customer[]>([])
|
||
const customerLoading = ref(false)
|
||
|
||
/** 物料选择 */
|
||
const showMaterialDialog = ref(false)
|
||
const materialSearch = ref('')
|
||
const materialList = ref<Material[]>([])
|
||
const materialLoading = ref(false)
|
||
const selectedMaterials = ref<Material[]>([])
|
||
const currentEditingRow = ref<SalesOrderLine | null>(null)
|
||
const materialTableRef = ref()
|
||
const selectedRadioId = ref<number | undefined>(undefined)
|
||
|
||
/** 销售部门、销售人员选项 */
|
||
const deptOptions = ref<Dept[]>([])
|
||
const userOptions = ref<User[]>([])
|
||
|
||
// ============ 计算属性 ============
|
||
|
||
/** 销售人员选项:有选中部门时仅显示该部门用户,否则显示全部 */
|
||
const salesmanOptions = computed(() => {
|
||
const list = userOptions.value
|
||
const deptId = formData.deptId
|
||
if (deptId) {
|
||
return list.filter(u => u.deptId === deptId)
|
||
}
|
||
return list
|
||
})
|
||
|
||
/** 合计金额 */
|
||
const totalAmount = computed(() => {
|
||
return (formData.lines || []).reduce((sum, line) => sum + (line.amount || 0), 0)
|
||
})
|
||
|
||
/** 是否可审核 */
|
||
const canAudit = computed(() => {
|
||
return (isEdit.value || isView.value) &&
|
||
formData.orderStatus === '开立' &&
|
||
(formData.lines?.length || 0) > 0
|
||
})
|
||
|
||
/** 是否可反审核 */
|
||
const canUnaudit = computed(() => {
|
||
return (isEdit.value || isView.value) && formData.orderStatus === '审核'
|
||
})
|
||
|
||
// ============ 方法 ============
|
||
|
||
/** 获取状态标签类型 */
|
||
function getStatusType(status?: string): string {
|
||
return ORDER_STATUS_MAP[status || '']?.type || ''
|
||
}
|
||
|
||
/** 获取状态标签文本 */
|
||
function getStatusLabel(status?: string): string {
|
||
return ORDER_STATUS_MAP[status || '']?.label || status || '开立'
|
||
}
|
||
|
||
/** 格式化数字 */
|
||
function formatNumber(val: number | undefined): string {
|
||
if (val === undefined || val === null) return '-'
|
||
return val.toLocaleString()
|
||
}
|
||
|
||
/** 格式化金额 */
|
||
function formatCurrency(val: number | undefined): string {
|
||
if (val === undefined || val === null) return '-'
|
||
return '¥' + val.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
||
}
|
||
|
||
/** 切换折叠 */
|
||
function toggleCollapse() {
|
||
isCollapsed.value = !isCollapsed.value
|
||
activeCollapse.value = isCollapsed.value ? [] : ['basic']
|
||
}
|
||
|
||
/** 加载订单数据 */
|
||
async function loadOrderData() {
|
||
const id = route.params.id as string
|
||
if (!id) return
|
||
|
||
try {
|
||
const data = await getSalesOrderDetail(Number(id))
|
||
|
||
// 先处理明细行,确保 lines 数组正确赋值
|
||
const lines = data.lines || data.slaveList || []
|
||
|
||
// 使用解构赋值确保响应式更新
|
||
Object.keys(data).forEach(key => {
|
||
if (key !== 'lines' && key !== 'slaveList') {
|
||
(formData as any)[key] = (data as any)[key]
|
||
}
|
||
})
|
||
|
||
// 明确赋值 lines 数组
|
||
formData.lines = lines.map((line: SalesOrderLine) => ({
|
||
lineId: line.lineId,
|
||
orderId: line.orderId,
|
||
itemId: line.itemId,
|
||
itemCode: line.itemCode || '',
|
||
itemName: line.itemName || '',
|
||
specification: line.specification || '',
|
||
unitOfMeasure: line.unitOfMeasure || '',
|
||
quantity: line.quantity,
|
||
unitPrice: line.unitPrice || 0,
|
||
amount: line.amount || 0,
|
||
deliveredQty: line.deliveredQty,
|
||
qualityReq: line.qualityReq || '',
|
||
remark: line.remark || '',
|
||
delFlag: line.delFlag
|
||
}))
|
||
|
||
console.log('加载订单明细:', formData.lines)
|
||
} catch (error) {
|
||
console.error('加载订单失败:', error)
|
||
ElMessage.error('加载订单数据失败')
|
||
}
|
||
}
|
||
|
||
/** 在部门树中递归查找指定部门 */
|
||
function findDeptInTree(list: Dept[], deptId: number): Dept | undefined {
|
||
for (const d of list) {
|
||
if (d.deptId === deptId) return d
|
||
if (d.children?.length) {
|
||
const found = findDeptInTree(d.children, deptId)
|
||
if (found) return found
|
||
}
|
||
}
|
||
return undefined
|
||
}
|
||
|
||
/** 加载部门列表(树形结构) */
|
||
async function loadDeptList() {
|
||
try {
|
||
const res: any = await listDept()
|
||
const data = res?.data ?? res
|
||
const raw = Array.isArray(data) ? data : (data?.list ?? data?.rows ?? [])
|
||
deptOptions.value = handleTree(raw)
|
||
} catch (e) {
|
||
console.error('加载部门列表失败', e)
|
||
deptOptions.value = []
|
||
}
|
||
}
|
||
|
||
/** 加载用户列表(用于销售人员下拉) */
|
||
async function loadUserList() {
|
||
try {
|
||
const res = await listUser({ status: '0', pageSize: 500 })
|
||
userOptions.value = res?.rows ?? []
|
||
} catch (e) {
|
||
console.error('加载用户列表失败', e)
|
||
userOptions.value = []
|
||
}
|
||
}
|
||
|
||
/** 销售部门变更:同步名称,清空不在该部门下的销售人员 */
|
||
function onDeptChange(deptId: number | undefined) {
|
||
if (deptId) {
|
||
const dept = findDeptInTree(deptOptions.value, deptId)
|
||
formData.deptName = dept?.deptName ?? ''
|
||
const inDept = salesmanOptions.value.some(u => u.userId === formData.salesmanId)
|
||
if (!inDept) {
|
||
formData.salesmanId = undefined
|
||
formData.salesmanName = ''
|
||
}
|
||
} else {
|
||
formData.deptName = ''
|
||
}
|
||
}
|
||
|
||
/** 销售人员变更:同步名称 */
|
||
function onSalesmanChange(userId: number | undefined) {
|
||
if (userId) {
|
||
const user = userOptions.value.find(u => u.userId === userId)
|
||
formData.salesmanName = user?.userName ?? user?.nickName ?? ''
|
||
} else {
|
||
formData.salesmanName = ''
|
||
}
|
||
}
|
||
|
||
/** 加载客户列表 */
|
||
async function loadCustomers() {
|
||
customerLoading.value = true
|
||
try {
|
||
const res = await getCustomerList({ clientName: customerSearch.value })
|
||
customerList.value = res.list
|
||
} catch (error) {
|
||
console.error('加载客户失败:', error)
|
||
} finally {
|
||
customerLoading.value = false
|
||
}
|
||
}
|
||
|
||
/** 选择客户 */
|
||
function handleSelectCustomer(row: Customer) {
|
||
formData.clientId = row.clientId || row.id
|
||
formData.clientCode = row.clientCode || row.code
|
||
formData.clientName = row.clientName || row.name
|
||
formData.receiver = row.contact1 || row.contact
|
||
formData.receiverPhone = row.contact1Tel || row.phone
|
||
formData.receiverAddress = row.address
|
||
showCustomerDialog.value = false
|
||
}
|
||
|
||
/** 加载物料列表 */
|
||
async function loadMaterials() {
|
||
materialLoading.value = true
|
||
try {
|
||
const searchText = materialSearch.value?.trim() || ''
|
||
// 支持按编码或名称搜索;仅显示产品物料(产品物料标识=PRODUCT)
|
||
const params: any = {
|
||
pageSize: 100, // 物料选择对话框不分页,一次性加载更多
|
||
itemOrProduct: 'PRODUCT',
|
||
enableFlag: 'Y' // 只显示启用状态的物料
|
||
}
|
||
|
||
if (searchText) {
|
||
// 如果搜索文本是纯数字或包含字母,可能是编码
|
||
// 否则按名称搜索
|
||
if (/^[A-Za-z0-9]+$/.test(searchText)) {
|
||
params.itemCode = searchText
|
||
} else {
|
||
params.itemName = searchText
|
||
}
|
||
}
|
||
|
||
const res = await getMaterialList(params)
|
||
materialList.value = res.list
|
||
} catch (error) {
|
||
console.error('加载物料失败:', error)
|
||
ElMessage.error('加载物料列表失败')
|
||
materialList.value = []
|
||
} finally {
|
||
materialLoading.value = false
|
||
}
|
||
}
|
||
|
||
/** 物料选择变化 */
|
||
function handleMaterialSelectionChange(rows: Material[]) {
|
||
if (currentEditingRow.value) {
|
||
// 单行选择模式:只保留最后一个选择
|
||
const lastRow = rows[rows.length - 1]
|
||
selectedMaterials.value = lastRow ? [lastRow] : []
|
||
} else {
|
||
// 批量添加模式:保留所有选择
|
||
selectedMaterials.value = [...rows]
|
||
}
|
||
}
|
||
|
||
/** 单选框选择(单行模式) */
|
||
function handleRadioSelect(row: Material) {
|
||
selectedMaterials.value = [row]
|
||
}
|
||
|
||
/** 物料行点击(单行选择模式) */
|
||
function handleMaterialRowClick(row: Material) {
|
||
if (currentEditingRow.value) {
|
||
// 单行选择模式:选中该行并同步单选框
|
||
selectedRadioId.value = row.id
|
||
selectedMaterials.value = [row]
|
||
}
|
||
}
|
||
|
||
/** 取消物料选择对话框 */
|
||
function handleCancelMaterialDialog() {
|
||
showMaterialDialog.value = false
|
||
selectedMaterials.value = []
|
||
currentEditingRow.value = null
|
||
}
|
||
|
||
/** 确认选择物料 */
|
||
function handleConfirmMaterials() {
|
||
if (selectedMaterials.value.length === 0) {
|
||
ElMessage.warning('请选择物料')
|
||
return
|
||
}
|
||
|
||
if (currentEditingRow.value) {
|
||
// 单行选择模式:更新当前行
|
||
const m = selectedMaterials.value[0]
|
||
if (m) {
|
||
currentEditingRow.value.itemId = m.id
|
||
currentEditingRow.value.itemCode = m.code
|
||
currentEditingRow.value.itemName = m.name
|
||
currentEditingRow.value.specification = m.spec || ''
|
||
currentEditingRow.value.unitOfMeasure = m.unit
|
||
// 保持数量和单价不变或默认,让用户手动输入
|
||
if (currentEditingRow.value.quantity === undefined) {
|
||
currentEditingRow.value.quantity = undefined
|
||
}
|
||
if (currentEditingRow.value.unitPrice === undefined || currentEditingRow.value.unitPrice === 0) {
|
||
currentEditingRow.value.unitPrice = 0
|
||
}
|
||
calculateLineAmount(currentEditingRow.value)
|
||
}
|
||
currentEditingRow.value = null
|
||
} else {
|
||
// 批量添加模式:为每个选中的物料创建新行
|
||
const newLines: SalesOrderLine[] = selectedMaterials.value.map(m => ({
|
||
itemId: m.id,
|
||
itemCode: m.code,
|
||
itemName: m.name,
|
||
specification: m.spec || '',
|
||
unitOfMeasure: m.unit,
|
||
quantity: undefined,
|
||
unitPrice: 0,
|
||
amount: 0,
|
||
qualityReq: '',
|
||
remark: ''
|
||
}))
|
||
|
||
// 过滤掉已存在的空行(没有选择物料的行)
|
||
const existingValidLines = (formData.lines || []).filter(line => line.itemId && line.itemCode)
|
||
formData.lines = [...existingValidLines, ...newLines]
|
||
|
||
ElMessage.success(`成功添加 ${newLines.length} 个物料`)
|
||
}
|
||
|
||
showMaterialDialog.value = false
|
||
selectedMaterials.value = []
|
||
}
|
||
|
||
/** 添加空行 */
|
||
function handleAddEmptyLine() {
|
||
const newLine: SalesOrderLine = {
|
||
itemId: undefined,
|
||
itemCode: '',
|
||
itemName: '',
|
||
specification: '',
|
||
unitOfMeasure: '',
|
||
quantity: undefined, // 默认为空,让用户手动输入
|
||
unitPrice: 0,
|
||
amount: 0,
|
||
qualityReq: '',
|
||
remark: ''
|
||
}
|
||
formData.lines = [...(formData.lines || []), newLine]
|
||
}
|
||
|
||
/** 批量新增物料(打开多选对话框) */
|
||
function handleBatchAddMaterial() {
|
||
currentEditingRow.value = null // null表示批量添加模式
|
||
selectedMaterials.value = []
|
||
materialSearch.value = ''
|
||
showMaterialDialog.value = true
|
||
}
|
||
|
||
/** 为单行选择物料 */
|
||
function selectMaterialForRow(row: SalesOrderLine) {
|
||
currentEditingRow.value = row
|
||
selectedMaterials.value = []
|
||
materialSearch.value = ''
|
||
showMaterialDialog.value = true
|
||
}
|
||
|
||
/** 计算行金额 */
|
||
function calculateLineAmount(row: SalesOrderLine) {
|
||
row.amount = (row.quantity || 0) * (row.unitPrice || 0)
|
||
}
|
||
|
||
/** 删除明细行 */
|
||
function handleDeleteLine(index: number) {
|
||
formData.lines?.splice(index, 1)
|
||
}
|
||
|
||
/** 引入(从合同引入物料) */
|
||
function handleImport() {
|
||
ElMessage.info('引入功能开发中')
|
||
}
|
||
|
||
/** 上传合同 */
|
||
function handleUploadContract() {
|
||
ElMessage.info('上传合同功能开发中')
|
||
}
|
||
|
||
/** 返回列表 */
|
||
function handleBack() {
|
||
router.push('/sales/order')
|
||
}
|
||
|
||
/** 保存 */
|
||
async function handleSave() {
|
||
try {
|
||
await formRef.value?.validate()
|
||
|
||
// 过滤空行(没有选择物料的行)
|
||
const validLines = (formData.lines || []).filter(
|
||
(line): line is SalesOrderLine => Boolean(line && line.itemId && line.itemCode)
|
||
)
|
||
|
||
if (validLines.length === 0) {
|
||
ElMessage.warning('请添加物料明细')
|
||
return
|
||
}
|
||
|
||
// 验证每行的数量
|
||
for (const [i, line] of validLines.entries()) {
|
||
if (!line.quantity || line.quantity <= 0) {
|
||
ElMessage.warning(`第 ${i + 1} 行物料数量必须大于0`)
|
||
return
|
||
}
|
||
}
|
||
|
||
saveLoading.value = true
|
||
|
||
// 更新表单中的有效行
|
||
formData.lines = validLines
|
||
|
||
// 更新总金额
|
||
formData.totalAmount = totalAmount.value
|
||
|
||
if (isAdd.value) {
|
||
const res = await createSalesOrder(formData)
|
||
ElMessage.success('保存成功')
|
||
// 跳转到编辑页
|
||
router.replace(`/sales/order/form/${res.orderId}`)
|
||
} else {
|
||
if (formData.orderId == null) {
|
||
ElMessage.warning('订单ID缺失,请刷新后重试或使用新增保存')
|
||
return
|
||
}
|
||
await updateSalesOrder(formData)
|
||
ElMessage.success('保存成功')
|
||
}
|
||
} catch (error: any) {
|
||
if (error !== false) {
|
||
console.error('保存失败:', error)
|
||
ElMessage.error('保存失败')
|
||
}
|
||
} finally {
|
||
saveLoading.value = false
|
||
}
|
||
}
|
||
|
||
/** 撤回 */
|
||
async function handleWithdraw() {
|
||
if (formData.orderId == null) {
|
||
ElMessage.warning('订单ID缺失,无法撤回')
|
||
return
|
||
}
|
||
try {
|
||
await ElMessageBox.confirm('确认撤回此订单吗?', '提示', { type: 'warning' })
|
||
formData.orderStatus = '草稿'
|
||
await updateSalesOrder(formData)
|
||
ElMessage.success('撤回成功')
|
||
loadOrderData()
|
||
} catch (error: any) {
|
||
if (error !== 'cancel') {
|
||
ElMessage.error('撤回失败')
|
||
}
|
||
}
|
||
}
|
||
|
||
/** 审核 */
|
||
async function handleAudit() {
|
||
if (formData.orderId == null) {
|
||
ElMessage.warning('请先保存订单后再审核')
|
||
return
|
||
}
|
||
try {
|
||
await ElMessageBox.confirm('确认审核此订单吗?', '提示', { type: 'info' })
|
||
await auditSalesOrder(formData.orderId)
|
||
ElMessage.success('审核成功')
|
||
loadOrderData()
|
||
} catch (error: any) {
|
||
if (error !== 'cancel') {
|
||
ElMessage.error(error?.message || '审核失败')
|
||
}
|
||
}
|
||
}
|
||
|
||
/** 反审核 */
|
||
async function handleUnaudit() {
|
||
if (formData.orderId == null) {
|
||
ElMessage.warning('订单ID异常,无法反审核')
|
||
return
|
||
}
|
||
try {
|
||
await ElMessageBox.confirm('确认反审核此订单吗?', '警告', { type: 'warning' })
|
||
await unauditSalesOrder(formData.orderId)
|
||
ElMessage.success('反审核成功')
|
||
loadOrderData()
|
||
} catch (error: any) {
|
||
if (error !== 'cancel') {
|
||
ElMessage.error(error?.message || '反审核失败')
|
||
}
|
||
}
|
||
}
|
||
|
||
/** 打印 */
|
||
const showPrintDialog = ref(false)
|
||
|
||
const printConfig = computed<PrintConfig>(() => ({
|
||
title: '销售订单',
|
||
qrCodeValue: formData.orderCode || undefined,
|
||
headerFields: [
|
||
{ label: '单据编码', value: formData.orderCode || '' },
|
||
{ label: '单据日期', value: formData.orderDate || '' },
|
||
{ label: '单据状态', value: getStatusLabel(formData.orderStatus) },
|
||
{ label: '业务类型', value: bizTypeOptions.find(o => o.value === formData.bizType)?.label || formData.bizType || '' },
|
||
{ label: '客户名称', value: formData.clientName || '' },
|
||
{ label: '销售人员', value: formData.salesmanName || '' },
|
||
{ label: '销售部门', value: formData.deptName || '' },
|
||
{ label: '合同号', value: formData.contractNo || '' },
|
||
{ label: '交付日期', value: formData.deliveryDate || '' },
|
||
{ label: '付款条件', value: formData.paymentTerms || '' },
|
||
{ label: '收货人', value: formData.receiver || '' },
|
||
{ label: '收货电话', value: formData.receiverPhone || '' },
|
||
{ label: '收货地址', value: formData.receiverAddress || '', span: 2 },
|
||
{ label: '备注', value: formData.remark || '', span: 2 }
|
||
],
|
||
columns: [
|
||
{ prop: '_index', label: '序号', type: 'index', width: '50px', align: 'center' },
|
||
{ prop: 'itemCode', label: '物料编码', width: '120px' },
|
||
{ prop: 'itemName', label: '物料名称', width: '150px' },
|
||
{ prop: 'specification', label: '型号规格', width: '120px' },
|
||
{ prop: 'unitOfMeasure', label: '主计量', width: '70px', align: 'center' },
|
||
{ prop: 'quantity', label: '数量', width: '80px', align: 'right' },
|
||
{ prop: 'unitPrice', label: '单价', width: '90px', align: 'right', type: 'amount' },
|
||
{ prop: 'amount', label: '金额', width: '100px', align: 'right', type: 'amount' },
|
||
{ prop: 'qualityReq', label: '质量要求', width: '100px' },
|
||
{ prop: 'remark', label: '备注' }
|
||
],
|
||
data: formData.lines || [],
|
||
summaries: [
|
||
{ label: '总金额', value: formatCurrency(totalAmount.value) }
|
||
],
|
||
footer: {
|
||
creator: formData.operatorName || formData.createBy || '',
|
||
approver: formData.auditorName || ''
|
||
}
|
||
}))
|
||
|
||
function handlePrint() {
|
||
showPrintDialog.value = true
|
||
}
|
||
|
||
/** 编辑(从查看页跳转) */
|
||
function handleEdit() {
|
||
router.push(`/sales/order/form/${formData.orderId}`)
|
||
}
|
||
|
||
/** 新增(从查看/编辑页跳转) */
|
||
function handleAdd() {
|
||
router.push('/sales/order/form')
|
||
}
|
||
|
||
// ============ 监听 ============
|
||
|
||
watch(showCustomerDialog, (val) => {
|
||
if (val) loadCustomers()
|
||
})
|
||
|
||
watch(showMaterialDialog, (val) => {
|
||
if (val) {
|
||
// 打开对话框时加载物料列表
|
||
loadMaterials()
|
||
// 清空之前的选择
|
||
selectedMaterials.value = []
|
||
selectedRadioId.value = undefined
|
||
// 清空表格选择状态
|
||
nextTick(() => {
|
||
materialTableRef.value?.clearSelection()
|
||
})
|
||
} else {
|
||
// 关闭对话框时清空状态
|
||
selectedMaterials.value = []
|
||
selectedRadioId.value = undefined
|
||
currentEditingRow.value = null
|
||
materialSearch.value = ''
|
||
}
|
||
})
|
||
|
||
// ============ 生命周期 ============
|
||
|
||
onMounted(() => {
|
||
loadDeptList()
|
||
loadUserList()
|
||
if (route.params.id) {
|
||
loadOrderData()
|
||
} else {
|
||
// 新增模式:默认添加一个空行(与目标ERP系统一致)
|
||
handleAddEmptyLine()
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.form-container {
|
||
padding: 8px;
|
||
}
|
||
|
||
.page-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 12px 16px;
|
||
background: #fff;
|
||
border-radius: 4px;
|
||
margin-bottom: 12px;
|
||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.header-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
}
|
||
|
||
.page-title {
|
||
font-size: 16px;
|
||
font-weight: 500;
|
||
color: #303133;
|
||
}
|
||
|
||
.header-right {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
.main-card :deep(.el-card__body) {
|
||
padding: 16px;
|
||
}
|
||
|
||
.main-card :deep(.el-collapse-item__header) {
|
||
font-weight: 500;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.main-card :deep(.el-collapse-item__content) {
|
||
padding-top: 16px;
|
||
}
|
||
|
||
.material-section {
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.section-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 12px;
|
||
padding-bottom: 8px;
|
||
border-bottom: 1px solid #ebeef5;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
color: #303133;
|
||
}
|
||
|
||
.section-toolbar {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
.summary-row {
|
||
margin-top: 16px;
|
||
text-align: right;
|
||
font-size: 14px;
|
||
padding: 12px 16px;
|
||
background: #f5f7fa;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.total-amount {
|
||
font-size: 20px;
|
||
font-weight: bold;
|
||
color: #f56c6c;
|
||
margin-left: 8px;
|
||
}
|
||
|
||
.dialog-search {
|
||
margin-bottom: 16px;
|
||
}
|
||
</style>
|