增加可修改计费重功能
This commit is contained in:
@@ -83,9 +83,26 @@
|
||||
@click="openBatchFee"
|
||||
>批量增加费用</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="Upload"
|
||||
v-hasPermi="['price:billing:chargeWeightEdit']"
|
||||
@click="openChargeWeightImport"
|
||||
>批量修改计费重</el-button>
|
||||
</el-col>
|
||||
</template>
|
||||
<template #col-action="{ row }">
|
||||
<el-button link type="primary" icon="View" @click="handleView(row)">查看</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
icon="Edit"
|
||||
:disabled="row.refundStatus === 'REFUNDED'"
|
||||
v-hasPermi="['price:billing:chargeWeightEdit']"
|
||||
@click="openEditChargeWeight(row)"
|
||||
>改计费重</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@@ -106,15 +123,61 @@
|
||||
:selected-ids="batchFeeSelectedIds"
|
||||
@success="onBatchFeeSuccess"
|
||||
/>
|
||||
<edit-charge-weight-dialog
|
||||
v-model="editCwOpen"
|
||||
:bill-id="editCwRow?.billId || ''"
|
||||
:order-no="editCwRow?.orderNo || ''"
|
||||
:origin-weight="editCwRow?.weight"
|
||||
:current-charge-weight="editCwRow?.chargeWeight"
|
||||
@success="onChargeWeightUpdated"
|
||||
/>
|
||||
<excel-import-dialog
|
||||
ref="chargeWeightImportRef"
|
||||
title="批量修改计费重"
|
||||
width="520px"
|
||||
action="/price/billing/bill/chargeWeight/importData"
|
||||
template-file-name="批量修改计费重导入模板"
|
||||
:show-update-support="false"
|
||||
:import-desc="chargeWeightImportDesc"
|
||||
:template-download-handler="downloadChargeWeightImportTemplate"
|
||||
:submit-handler="runChargeWeightImportAsync"
|
||||
@success="onChargeWeightImportSuccess"
|
||||
>
|
||||
<template #extra>
|
||||
<el-checkbox v-model="cwFillByFilter">按当前筛选条件带出订单号和客户</el-checkbox>
|
||||
<div class="cw-fill-hint">勾选后下载模板会写入筛选结果的订单号、客户代码;人工修改计费重列留空待填</div>
|
||||
</template>
|
||||
</excel-import-dialog>
|
||||
<el-dialog
|
||||
v-model="cwImportProgressVisible"
|
||||
title="正在导入修改计费重"
|
||||
width="420px"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
:show-close="false"
|
||||
append-to-body
|
||||
>
|
||||
<div class="import-progress">
|
||||
<el-progress
|
||||
:percentage="cwImportProgressPercent"
|
||||
:status="cwImportProgressStatus"
|
||||
:indeterminate="cwImportProgressIndeterminate"
|
||||
:stroke-width="10"
|
||||
/>
|
||||
<div class="import-progress-msg">{{ cwImportProgressMessage }}</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="BillingBill">
|
||||
import { listBill, getBill, refundBill, listOrderNoOptions, listTrackingNoOptions, listTransferNoOptions, updateCreditArrears } from '@/api/price/billing'
|
||||
import { listBill, getBill, refundBill, listOrderNoOptions, listTrackingNoOptions, listTransferNoOptions, updateCreditArrears, importChargeWeightAsync, downloadChargeWeightTemplate } from '@/api/price/billing'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { useProducts } from '@/components/ProductSelect/useProducts'
|
||||
import BillDetail from './components/BillDetail.vue'
|
||||
import BatchAddIncomeFeeDialog from './components/BatchAddIncomeFeeDialog.vue'
|
||||
import EditChargeWeightDialog from './components/EditChargeWeightDialog.vue'
|
||||
import ExcelImportDialog from '@/components/ExcelImportDialog'
|
||||
|
||||
// 订单号下拉远程搜索:返回匹配的订单号列表(最多 20 条)
|
||||
function searchOrderNoOptions(keyword) {
|
||||
@@ -149,6 +212,21 @@ const batchFeeOpen = ref(false)
|
||||
const batchFeeListQuery = ref({})
|
||||
const batchFeeSelectedIds = ref([])
|
||||
const refundingKey = ref(null)
|
||||
const editCwOpen = ref(false)
|
||||
const editCwRow = ref(null)
|
||||
const chargeWeightImportRef = ref()
|
||||
const chargeWeightImportDesc = [
|
||||
'模板列:订单号、客户、人工修改计费重(kg)',
|
||||
'客户可填名称或代码,须与订单客户一致',
|
||||
'导入后异步写入计费重并重新计费;原计费重(weight)不覆盖',
|
||||
'下载模板可选:按当前列表筛选条件带出订单号和客户'
|
||||
]
|
||||
const cwFillByFilter = ref(false)
|
||||
const cwImportProgressVisible = ref(false)
|
||||
const cwImportProgressPercent = ref(0)
|
||||
const cwImportProgressMessage = ref('')
|
||||
const cwImportProgressStatus = ref(undefined)
|
||||
const cwImportProgressIndeterminate = ref(false)
|
||||
|
||||
const columns = [
|
||||
{ label: '订单号', prop: 'orderNo', minWidth: 140, showOverflowTooltip: true },
|
||||
@@ -160,6 +238,7 @@ const columns = [
|
||||
{ label: '国家', prop: 'country', minWidth: 130, slot: 'country' },
|
||||
{ label: '件数', prop: 'pieces', align: 'center', width: 70 },
|
||||
{ label: '计费重', prop: 'chargeWeight', align: 'center', width: 90, slot: 'chargeWeight' },
|
||||
{ label: '原计费重', prop: 'weight', align: 'center', width: 100, slot: 'chargeWeight' },
|
||||
{ label: '收货重', prop: 'receiveWeight', align: 'center', width: 90, slot: 'chargeWeight' },
|
||||
{ label: '应收总额', prop: 'incomeTotal', align: 'right', width: 120, slot: 'incomeTotal' },
|
||||
{ label: '应付总额', prop: 'payTotal', align: 'right', width: 120, slot: 'payTotal' },
|
||||
@@ -172,7 +251,7 @@ const columns = [
|
||||
{ label: '销售员', prop: 'belongSaler', minWidth: 100, showOverflowTooltip: true },
|
||||
{ label: '结算员', prop: 'settlementMan', minWidth: 100, showOverflowTooltip: true },
|
||||
// { label: '客服员', prop: 'serviceMan', minWidth: 100, showOverflowTooltip: true },
|
||||
{ label: '操作', prop: 'action', align: 'center', width: 160, fixed: 'right', slot: 'action', showInToolbar: false }
|
||||
{ label: '操作', prop: 'action', align: 'center', width: 240, fixed: 'right', slot: 'action', showInToolbar: false }
|
||||
]
|
||||
|
||||
const arrearsStatusOptions = [
|
||||
@@ -316,6 +395,120 @@ function onBatchFeeSuccess() {
|
||||
tableRef.value?.refresh?.()
|
||||
}
|
||||
|
||||
function openEditChargeWeight(row) {
|
||||
if (!row || !row.billId) return
|
||||
if (row.refundStatus === 'REFUNDED') {
|
||||
ElMessage.warning('该订单已退费')
|
||||
return
|
||||
}
|
||||
editCwRow.value = row
|
||||
editCwOpen.value = true
|
||||
}
|
||||
|
||||
function onChargeWeightUpdated() {
|
||||
tableRef.value?.refresh?.()
|
||||
}
|
||||
|
||||
function buildChargeWeightTemplateQuery() {
|
||||
const q = tableRef.value?.getQueryParams?.() || {}
|
||||
const { pageNum, pageSize, bizDateRange, ...rest } = q
|
||||
const payload = {
|
||||
fillByFilter: !!cwFillByFilter.value,
|
||||
orderNos: rest.orderNos || [],
|
||||
trackingNos: rest.trackingNos || [],
|
||||
transferNos: rest.transferNos || [],
|
||||
customerCodes: rest.customerCodes || [],
|
||||
productCodes: rest.productCodes || [],
|
||||
creditArrearsStatus: rest.creditArrearsStatus || undefined
|
||||
}
|
||||
if (Array.isArray(bizDateRange) && (bizDateRange[0] || bizDateRange[1])) {
|
||||
payload.beginBizDate = bizDateRange[0] || null
|
||||
payload.endBizDate = bizDateRange[1] || null
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
async function downloadChargeWeightImportTemplate() {
|
||||
const payload = buildChargeWeightTemplateQuery()
|
||||
if (payload.fillByFilter && !hasBatchFeeQuery(tableRef.value?.getQueryParams?.() || {})) {
|
||||
throw new Error('请先设置列表筛选条件')
|
||||
}
|
||||
await downloadChargeWeightTemplate(payload)
|
||||
}
|
||||
|
||||
function openChargeWeightImport() {
|
||||
cwFillByFilter.value = false
|
||||
chargeWeightImportRef.value?.open?.()
|
||||
}
|
||||
|
||||
function applyCwImportProgress(t) {
|
||||
cwImportProgressIndeterminate.value = false
|
||||
cwImportProgressPercent.value = Math.min(100, Number(t.percent) || 0)
|
||||
const total = Number(t.total) || 0
|
||||
const success = Number(t.success) || 0
|
||||
const fail = Number(t.fail) || 0
|
||||
if (t.status === 'PENDING') {
|
||||
cwImportProgressMessage.value = '\u6392\u961f\u4e2d\u2026'
|
||||
} else if (t.status === 'RUNNING') {
|
||||
if (total > 0) {
|
||||
cwImportProgressMessage.value = '\u6b63\u5728\u5904\u7406 ' + success + '/' + total +
|
||||
(fail ? ('\uff08\u5931\u8d25 ' + fail + '\uff09') : '')
|
||||
} else {
|
||||
cwImportProgressMessage.value = t.message || '\u6b63\u5728\u5bfc\u5165\u2026'
|
||||
}
|
||||
} else if (t.status === 'SUCCESS') {
|
||||
cwImportProgressMessage.value = '\u5b8c\u6210\uff1a\u6210\u529f ' + success + '\uff0c\u5931\u8d25 ' + fail
|
||||
cwImportProgressStatus.value = fail > 0 ? 'warning' : 'success'
|
||||
} else if (t.status === 'FAIL') {
|
||||
cwImportProgressMessage.value = t.error || t.message || '\u5bfc\u5165\u5931\u8d25'
|
||||
cwImportProgressStatus.value = 'exception'
|
||||
}
|
||||
}
|
||||
|
||||
async function runChargeWeightImportAsync(uploadFile) {
|
||||
const raw = uploadFile && (uploadFile.raw || uploadFile)
|
||||
if (!raw) throw new Error('\u8bf7\u5148\u9009\u62e9\u6587\u4ef6')
|
||||
// Close import dialog so progress dialog is the only visible wait UI
|
||||
chargeWeightImportRef.value?.close?.()
|
||||
cwImportProgressVisible.value = true
|
||||
cwImportProgressPercent.value = 0
|
||||
cwImportProgressIndeterminate.value = true
|
||||
cwImportProgressMessage.value = '\u6b63\u5728\u89e3\u6790Excel\u5e76\u6821\u9a8c\u8ba2\u5355\uff0c\u8bf7\u7a0d\u5019\u2026'
|
||||
cwImportProgressStatus.value = undefined
|
||||
try {
|
||||
const { task } = await importChargeWeightAsync(raw, applyCwImportProgress)
|
||||
cwImportProgressIndeterminate.value = false
|
||||
cwImportProgressPercent.value = 100
|
||||
const success = Number(task && task.success) || 0
|
||||
const fail = Number(task && task.fail) || 0
|
||||
const tip = '\u5bfc\u5165\u5b8c\u6210\uff1a\u6210\u529f ' + success + '\uff0c\u5931\u8d25 ' + fail
|
||||
if (fail > 0) {
|
||||
cwImportProgressStatus.value = 'warning'
|
||||
cwImportProgressMessage.value = tip + (task && task.error ? ('\uff1b' + task.error) : '')
|
||||
await new Promise(resolve => setTimeout(resolve, 1500))
|
||||
return tip + (task && task.error ? ('\uff1b' + task.error) : '')
|
||||
}
|
||||
cwImportProgressStatus.value = 'success'
|
||||
cwImportProgressMessage.value = tip
|
||||
await new Promise(resolve => setTimeout(resolve, 1200))
|
||||
return tip
|
||||
} catch (e) {
|
||||
cwImportProgressIndeterminate.value = false
|
||||
cwImportProgressStatus.value = 'exception'
|
||||
const tip = (e && e.task && e.task.error) || (e && e.message) || '\u5bfc\u5165\u5931\u8d25'
|
||||
cwImportProgressMessage.value = tip
|
||||
await new Promise(resolve => setTimeout(resolve, 1200))
|
||||
throw e
|
||||
} finally {
|
||||
cwImportProgressVisible.value = false
|
||||
cwImportProgressIndeterminate.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function onChargeWeightImportSuccess() {
|
||||
tableRef.value?.refresh?.()
|
||||
}
|
||||
|
||||
function formatMoney(val) {
|
||||
if (val === null || val === undefined || val === '') return '-'
|
||||
const num = Number(val)
|
||||
@@ -388,6 +581,22 @@ function refundTagType(status) {
|
||||
font-weight: 600;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.cw-fill-hint {
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
line-height: 1.5;
|
||||
}
|
||||
.import-progress { padding: 8px 4px 4px; }
|
||||
.import-progress-msg {
|
||||
margin-top: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
word-break: break-word;
|
||||
max-height: 160px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* 搜索栏走 BasicTable search-per-row=4 的栅格布局(固定 4 项一行,多余换行),
|
||||
这里只补齐插槽渲染的控件宽度,不再覆盖 BasicForm 的布局 */
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
<el-descriptions class="detail-desc" :column="2" border size="small">
|
||||
<el-descriptions-item label="产品">{{ productLabel(detail) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="计费重量(kg)">{{ formatWeight(detail.chargeWeight ?? detail.weight) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="原计费重(kg)">{{ formatWeight(detail.weight) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="重量段">{{ detail.weightLabel || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="计费时间">{{ detail.bizDate || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="计价来源">
|
||||
@@ -94,6 +95,7 @@
|
||||
<el-tab-pane label="应收" name="income">
|
||||
<div class="fee-toolbar">
|
||||
<el-button type="primary" plain size="small" icon="Plus" v-hasPermi="['price:billing:feeAdd']" @click="openAddFee">增加费用</el-button>
|
||||
<el-button type="primary" plain size="small" icon="Edit" v-hasPermi="['price:billing:chargeWeightEdit']" :disabled="detail.refundStatus === 'REFUNDED'" @click="openEditCw">修改计费重</el-button>
|
||||
</div>
|
||||
<el-table v-if="incomeFees.length" :data="incomeFees" border size="small" class="fee-table">
|
||||
<el-table-column label="费用名称" min-width="140" show-overflow-tooltip>
|
||||
@@ -171,11 +173,20 @@
|
||||
:edit-row="editFeeRow"
|
||||
@success="onFeeAdded"
|
||||
/>
|
||||
<edit-charge-weight-dialog
|
||||
v-model="editCwOpen"
|
||||
:bill-id="detail?.billId || ''"
|
||||
:order-no="detail?.orderNo || ''"
|
||||
:origin-weight="detail?.weight"
|
||||
:current-charge-weight="detail?.chargeWeight"
|
||||
@success="onChargeWeightUpdated"
|
||||
/>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import AddIncomeFeeDialog from './AddIncomeFeeDialog.vue'
|
||||
import EditChargeWeightDialog from './EditChargeWeightDialog.vue'
|
||||
import { deleteIncomeBillFee } from '@/api/price/billing'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
@@ -192,6 +203,7 @@ const emit = defineEmits(['update:modelValue', 'updated'])
|
||||
const feeTab = ref('income')
|
||||
const addFeeOpen = ref(false)
|
||||
const editFeeRow = ref(null)
|
||||
const editCwOpen = ref(false)
|
||||
const { bms_fee_type, bms_currency } = useDict('bms_fee_type', 'bms_currency')
|
||||
|
||||
const incomeFees = computed(() => {
|
||||
@@ -240,6 +252,16 @@ function onFeeAdded(data) {
|
||||
if (data) emit('updated', data)
|
||||
}
|
||||
|
||||
function openEditCw() {
|
||||
if (!props.detail?.billId) return
|
||||
if (props.detail.refundStatus === 'REFUNDED') return
|
||||
editCwOpen.value = true
|
||||
}
|
||||
|
||||
function onChargeWeightUpdated(data) {
|
||||
if (data) emit('updated', data)
|
||||
}
|
||||
|
||||
|
||||
function customerActiveLabel(status) {
|
||||
const map = { ACTIVE: '正常', MISSING: '不存在', DISABLED: '停用' }
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:title="dlgTitle"
|
||||
v-model="visible"
|
||||
width="420px"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
@closed="onClosed"
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
|
||||
<el-form-item label="订单号">
|
||||
<span>{{ orderNo || '-' }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="原计费重(kg)">
|
||||
<span>{{ formatWeight(originWeight) }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="当前计费重(kg)">
|
||||
<span>{{ formatWeight(currentChargeWeight) }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="人工修改计费重" prop="chargeWeight">
|
||||
<el-input-number
|
||||
v-model="form.chargeWeight"
|
||||
:min="0.001"
|
||||
:precision="3"
|
||||
:step="0.1"
|
||||
controls-position="right"
|
||||
style="width: 100%;"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button type="primary" :loading="submitting" @click="submit">确 定</el-button>
|
||||
<el-button :disabled="submitting" @click="visible = false">取 消</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { updateChargeWeight } from '@/api/price/billing'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const dlgTitle = '修改计费重'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: Boolean, default: false },
|
||||
billId: { type: String, default: '' },
|
||||
orderNo: { type: String, default: '' },
|
||||
originWeight: { type: [Number, String], default: null },
|
||||
currentChargeWeight: { type: [Number, String], default: null }
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'success'])
|
||||
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (v) => emit('update:modelValue', v)
|
||||
})
|
||||
|
||||
const formRef = ref()
|
||||
const submitting = ref(false)
|
||||
const form = reactive({ chargeWeight: undefined })
|
||||
|
||||
const rules = {
|
||||
chargeWeight: [
|
||||
{ required: true, message: '请输入人工修改计费重', trigger: 'blur' },
|
||||
{
|
||||
validator: (_r, v, cb) => {
|
||||
if (v == null || Number(v) <= 0) cb(new Error('计费重必须大于 0'))
|
||||
else cb()
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(open) => {
|
||||
if (!open) return
|
||||
const cur = props.currentChargeWeight
|
||||
form.chargeWeight = cur == null || cur === '' ? undefined : Number(cur)
|
||||
}
|
||||
)
|
||||
|
||||
function formatWeight(val) {
|
||||
if (val === null || val === undefined || val === '') return '-'
|
||||
const num = Number(val)
|
||||
if (Number.isNaN(num)) return val
|
||||
return num.toFixed(3)
|
||||
}
|
||||
|
||||
function onClosed() {
|
||||
form.chargeWeight = undefined
|
||||
formRef.value?.resetFields?.()
|
||||
submitting.value = false
|
||||
}
|
||||
|
||||
function submit() {
|
||||
formRef.value?.validate?.((ok) => {
|
||||
if (!ok) return
|
||||
if (!props.billId) return
|
||||
submitting.value = true
|
||||
updateChargeWeight({ billId: props.billId, chargeWeight: form.chargeWeight })
|
||||
.then((res) => {
|
||||
ElMessage.success('修改成功,已重新计费')
|
||||
emit('success', res.data || null)
|
||||
visible.value = false
|
||||
})
|
||||
.finally(() => {
|
||||
submitting.value = false
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user