提单费用均摊处理
This commit is contained in:
@@ -36,6 +36,43 @@ export function listAirBillFeeLog(query) {
|
||||
})
|
||||
}
|
||||
|
||||
/** 勾选提单后,查询需要分摊到订单的费用 */
|
||||
export function previewAirBillAlloc(billCodes) {
|
||||
return request({
|
||||
url: '/price/airBill/fee/alloc/preview',
|
||||
method: 'get',
|
||||
params: { billCodes: (billCodes || []).join(',') }
|
||||
})
|
||||
}
|
||||
|
||||
/** 按重量或订单数把费用分摊到订单 */
|
||||
export function allocateAirBillFee(billCodes) {
|
||||
return request({
|
||||
url: '/price/airBill/fee/alloc',
|
||||
method: 'post',
|
||||
data: { billCodes },
|
||||
timeout: 120000
|
||||
})
|
||||
}
|
||||
|
||||
/** 已分摊的费用改为已确定 */
|
||||
export function confirmAirBillFee(billCode, feeId) {
|
||||
return request({
|
||||
url: '/price/airBill/fee/confirm',
|
||||
method: 'post',
|
||||
params: { billCode, feeId }
|
||||
})
|
||||
}
|
||||
|
||||
/** 作废已分摊或已确定的费用,并逻辑删除订单上的分摊明细 */
|
||||
export function voidAirBillFee(billCode, feeId) {
|
||||
return request({
|
||||
url: '/price/airBill/fee/void',
|
||||
method: 'post',
|
||||
params: { billCode, feeId }
|
||||
})
|
||||
}
|
||||
|
||||
/** 只提交新增、修改、删除的费用 */
|
||||
export function saveAirBillFee(data) {
|
||||
return request({
|
||||
|
||||
@@ -238,6 +238,11 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 最多勾选条数,0 表示不限制
|
||||
selectionMax: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 是否显示序号列
|
||||
showIndex: {
|
||||
type: Boolean,
|
||||
@@ -436,10 +441,30 @@ function resetQuery() {
|
||||
getList()
|
||||
}
|
||||
|
||||
// 选中变化
|
||||
// 选中变化。超过上限时只保留先勾到的行,并同步虚拟表和普通表的勾选状态。
|
||||
let cappingSelection = false
|
||||
function handleSelectionChange(val) {
|
||||
selectionList.value = val
|
||||
emit('selection-change', val)
|
||||
if (cappingSelection) return
|
||||
const rows = Array.isArray(val) ? val.slice() : []
|
||||
const max = Number(props.selectionMax) || 0
|
||||
if (max > 0 && rows.length > max) {
|
||||
const kept = rows.slice(0, max)
|
||||
cappingSelection = true
|
||||
selectionList.value = kept
|
||||
emit('selection-change', kept)
|
||||
proxy.$modal.msgWarning(`单次最多勾选 ${max} 条`)
|
||||
nextTick(() => {
|
||||
const table = tableRef.value
|
||||
if (table?.toggleRowSelection) {
|
||||
const keys = new Set(kept.map((row) => virtualRowId(row)))
|
||||
tableData.value.forEach((row) => table.toggleRowSelection(row, keys.has(virtualRowId(row))))
|
||||
}
|
||||
cappingSelection = false
|
||||
})
|
||||
return
|
||||
}
|
||||
selectionList.value = rows
|
||||
emit('selection-change', rows)
|
||||
}
|
||||
const selectionIds = computed(() =>
|
||||
selectionList.value.map((r) => (props.rowKey ? r[props.rowKey] : r.id)).filter((v) => v !== undefined)
|
||||
|
||||
@@ -11,7 +11,20 @@
|
||||
:page-size="100"
|
||||
:height="tableHeight"
|
||||
virtual
|
||||
selection
|
||||
:selection-max="5"
|
||||
>
|
||||
<template #toolbar="{ selection }">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
:disabled="!selection.length"
|
||||
v-hasPermi="['price:airBill:fee']"
|
||||
@click="openAlloc(selection)"
|
||||
>费用处理</el-button>
|
||||
</el-col>
|
||||
</template>
|
||||
<template #search-billCodes="{ model }">
|
||||
<el-input
|
||||
v-model="model.billCodes"
|
||||
@@ -55,6 +68,7 @@
|
||||
|
||||
<air-bill-detail v-model="open" :title="title" :loading="loading" :detail="detail" />
|
||||
<air-bill-fee-dialog ref="feeRef" />
|
||||
<air-bill-alloc-dialog ref="allocRef" @done="tableRef?.refresh()" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -62,9 +76,11 @@
|
||||
import { listAirBill, getAirBill } from '@/api/price/airBill'
|
||||
import AirBillDetail from './components/AirBillDetail.vue'
|
||||
import AirBillFeeDialog from './components/AirBillFeeDialog.vue'
|
||||
import AirBillAllocDialog from './components/AirBillAllocDialog.vue'
|
||||
|
||||
const tableRef = ref()
|
||||
const feeRef = ref()
|
||||
const allocRef = ref()
|
||||
const tableHeight = ref(480)
|
||||
let tableResizeObserver = null
|
||||
|
||||
@@ -171,6 +187,10 @@ function openFee(row) {
|
||||
feeRef.value?.open(row)
|
||||
}
|
||||
|
||||
function openAlloc(rows) {
|
||||
allocRef.value?.open((rows || []).map(row => row.billCode))
|
||||
}
|
||||
|
||||
function openDetail(row) {
|
||||
if (!row?.billCode) return
|
||||
open.value = true
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" width="980px" append-to-body destroy-on-close @closed="reset">
|
||||
<template #header>
|
||||
<div class="alloc-title">
|
||||
<span>费用处理</span>
|
||||
<span class="alloc-hint">本次操作是将费用均摊到订单</span>
|
||||
</div>
|
||||
</template>
|
||||
<div v-loading="loading">
|
||||
<div class="alloc-summary">
|
||||
已选 {{ billCodes.length }} 个提单,待分摊 {{ rows.length }} 条费用
|
||||
</div>
|
||||
<el-alert
|
||||
v-if="blockText"
|
||||
:title="blockText"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="alloc-alert"
|
||||
/>
|
||||
<el-table :data="rows" border size="small" max-height="420" empty-text="所选提单没有需要分摊的费用">
|
||||
<el-table-column prop="billCode" label="提单号" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="费用阶段" width="150" align="center">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :options="bms_fee_level" :value="row.feeLevel" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="费用状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :options="bill_fee_status" :value="row.billFeeStatus || '0'" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="feeName" label="费用项" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="价格" width="110" align="right">
|
||||
<template #default="{ row }">{{ priceText(row.price) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="币种" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<dict-tag v-if="row.currency" :options="bms_currency" :value="row.currency" />
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分摊维度" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :options="bill_fee_alloc_dim" :value="row.allocDim" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="orderCount" label="订单数" width="80" align="center" />
|
||||
</el-table>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" :disabled="!canSubmit" :loading="saving" @click="submit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { previewAirBillAlloc, allocateAirBillFee } from '@/api/price/airBill'
|
||||
import { useDict } from '@/utils/dict'
|
||||
|
||||
const emit = defineEmits(['done'])
|
||||
const { proxy } = getCurrentInstance()
|
||||
const { bms_fee_level, bill_fee_status, bms_currency, bill_fee_alloc_dim } = useDict(
|
||||
'bms_fee_level', 'bill_fee_status', 'bms_currency', 'bill_fee_alloc_dim'
|
||||
)
|
||||
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const billCodes = ref([])
|
||||
const rows = ref([])
|
||||
|
||||
const blockText = computed(() => {
|
||||
const blocked = rows.value.filter(row => row.blockReason)
|
||||
if (!blocked.length) return ''
|
||||
return blocked.slice(0, 3).map(row => `${row.billCode || '-'} ${row.feeName || '费用'}:${row.blockReason}`).join(';')
|
||||
})
|
||||
const canSubmit = computed(() => !loading.value && rows.value.length > 0 && !blockText.value)
|
||||
|
||||
function open(codes) {
|
||||
const picked = [...new Set((codes || []).map(code => String(code || '').trim()).filter(Boolean))]
|
||||
if (!picked.length) {
|
||||
proxy.$modal.msgWarning('请先勾选提单')
|
||||
return
|
||||
}
|
||||
if (picked.length > 5) {
|
||||
proxy.$modal.msgWarning('单次最多勾选 5 条提单')
|
||||
return
|
||||
}
|
||||
billCodes.value = picked
|
||||
rows.value = []
|
||||
visible.value = true
|
||||
loading.value = true
|
||||
previewAirBillAlloc(picked).then(res => {
|
||||
rows.value = res.data || []
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function priceText(value) {
|
||||
if (value === null || value === undefined || value === '') return '-'
|
||||
const number = Number(value)
|
||||
return Number.isFinite(number) ? number.toFixed(2) : value
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
if (!canSubmit.value) return
|
||||
try {
|
||||
await proxy.$modal.confirm('确认将费用分摊到订单吗?确定后会按重量或订单数量摊到每一票,费用状态改为已分摊。')
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
saving.value = true
|
||||
try {
|
||||
const res = await allocateAirBillFee(billCodes.value)
|
||||
proxy.$modal.msgSuccess(res.msg || '分摊完成')
|
||||
visible.value = false
|
||||
emit('done')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function reset() {
|
||||
loading.value = false
|
||||
saving.value = false
|
||||
billCodes.value = []
|
||||
rows.value = []
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.alloc-title {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 12px;
|
||||
}
|
||||
.alloc-hint {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
.alloc-summary {
|
||||
margin-bottom: 12px;
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
.alloc-alert {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -2,14 +2,14 @@
|
||||
<el-dialog v-model="visible" :title="title" width="1360px" append-to-body destroy-on-close @closed="reset">
|
||||
<div v-loading="loading">
|
||||
<div class="fee-toolbar">
|
||||
<el-button type="primary" plain icon="Plus" @click="addRow">新增</el-button>
|
||||
<el-button type="primary" plain icon="Plus" v-hasPermi="['price:airBill:fee']" @click="addRow">新增</el-button>
|
||||
<span class="fee-hint">只保存本次新增、修改和删除的费用,未改动的行不会重写。</span>
|
||||
</div>
|
||||
<el-table :data="rows" border size="small" max-height="320" row-key="rowKey" empty-text="暂无费用,点击新增">
|
||||
<el-table-column min-width="180">
|
||||
<template #header><span class="col-label"><i v-if="allocRequired" class="req-star">*</i>费用阶段</span></template>
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.feeLevel" filterable clearable placeholder="请选择" style="width: 100%">
|
||||
<el-select v-model="row.feeLevel" filterable clearable placeholder="请选择" style="width: 100%" :disabled="locked(row)">
|
||||
<el-option v-for="item in bms_fee_level" :key="item.value" :label="item.label" :value="String(item.value)" />
|
||||
</el-select>
|
||||
</template>
|
||||
@@ -17,7 +17,7 @@
|
||||
<el-table-column width="120">
|
||||
<template #header><span class="col-label"><i class="req-star">*</i>是否分摊</span></template>
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.allocFlag" placeholder="请选择" style="width: 100%" @change="changeAlloc(row)">
|
||||
<el-select v-model="row.allocFlag" placeholder="请选择" style="width: 100%" :disabled="locked(row)" @change="changeAlloc(row)">
|
||||
<el-option v-for="item in bill_fee_alloc" :key="item.value" :label="item.label" :value="String(item.value)" />
|
||||
</el-select>
|
||||
</template>
|
||||
@@ -25,7 +25,7 @@
|
||||
<el-table-column v-if="allocRequired" min-width="150">
|
||||
<template #header><span class="col-label"><i class="req-star">*</i>分摊维度(票)</span></template>
|
||||
<template #default="{ row }">
|
||||
<el-select v-if="String(row.allocFlag) === '1'" v-model="row.allocDim" placeholder="请选择" style="width: 100%">
|
||||
<el-select v-if="String(row.allocFlag) === '1'" v-model="row.allocDim" placeholder="请选择" style="width: 100%" :disabled="locked(row)">
|
||||
<el-option v-for="item in bill_fee_alloc_dim" :key="item.value" :label="item.label" :value="String(item.value)" />
|
||||
</el-select>
|
||||
</template>
|
||||
@@ -39,7 +39,7 @@
|
||||
<template #header><span class="col-label"><i class="req-star">*</i>费用项</span></template>
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.feeType" filterable remote clearable reserve-keyword placeholder="输入费用名称"
|
||||
:remote-method="searchSurcharge" :loading="surchargeLoading" style="width: 100%" @change="name => onFeeChange(row, name)">
|
||||
:remote-method="searchSurcharge" :loading="surchargeLoading" :disabled="locked(row)" style="width: 100%" @change="name => onFeeChange(row, name)">
|
||||
<el-option v-for="item in surchargeOptions" :key="item.scId" :label="item.scName" :value="item.scId" />
|
||||
</el-select>
|
||||
</template>
|
||||
@@ -47,25 +47,27 @@
|
||||
<el-table-column width="150">
|
||||
<template #header><span class="col-label"><i class="req-star">*</i>价格</span></template>
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-model="row.price" :min="0" :precision="4" :controls="false" :value-on-clear="null" placeholder="价格" style="width: 100%" />
|
||||
<el-input-number v-model="row.price" :min="0" :precision="4" :controls="false" :value-on-clear="null" :disabled="locked(row)" placeholder="价格" style="width: 100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="140">
|
||||
<template #header><span class="col-label"><i class="req-star">*</i>收费币种</span></template>
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.currency" filterable clearable placeholder="请选择" style="width: 100%">
|
||||
<el-select v-model="row.currency" filterable clearable placeholder="请选择" style="width: 100%" :disabled="locked(row)">
|
||||
<el-option v-for="item in bms_currency" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="180">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" maxlength="500" clearable placeholder="备注" />
|
||||
<el-input v-model="row.remark" maxlength="500" clearable placeholder="备注" :disabled="locked(row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80" align="center">
|
||||
<el-table-column label="操作" width="180" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="danger" @click="removeRow(row)">删除</el-button>
|
||||
<el-button v-if="row.billFeeStatus === '1'" v-hasPermi="['price:airBill:feeConfirm']" link type="primary" :disabled="acting" @click="confirmRow(row)">确定</el-button>
|
||||
<el-button v-if="row.billFeeStatus === '1' || row.billFeeStatus === '3'" v-hasPermi="['price:airBill:feeVoid']" link type="danger" :disabled="acting" @click="voidRow(row)">作废</el-button>
|
||||
<el-button v-if="!locked(row)" v-hasPermi="['price:airBill:fee']" link type="danger" @click="removeRow(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -77,14 +79,14 @@
|
||||
<el-table-column label="操作时间" width="168">
|
||||
<template #default="{ row }">{{ logTime(row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作类型" width="90" align="center">
|
||||
<el-table-column label="操作类型" width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" :type="logType(row.operationType)">{{ logAction(row.operationType) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="更改前" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<div v-if="blankSide(row, 'old')" class="log-empty">空</div>
|
||||
<div v-if="blankSide(row, 'old')" class="log-empty">{{ emptyText(row) }}</div>
|
||||
<div v-else class="log-change">
|
||||
<div v-for="change in row.changes" :key="change.field">{{ change.fieldLabel || change.field }}:{{ showValue(change.oldValue) }}</div>
|
||||
</div>
|
||||
@@ -92,7 +94,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="更改后" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<div v-if="blankSide(row, 'new')" class="log-empty">空</div>
|
||||
<div v-if="blankSide(row, 'new')" class="log-empty">{{ emptyText(row) }}</div>
|
||||
<div v-else class="log-change">
|
||||
<div v-for="change in row.changes" :key="change.field">{{ change.fieldLabel || change.field }}:{{ showValue(change.newValue) }}</div>
|
||||
</div>
|
||||
@@ -117,13 +119,13 @@
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" :disabled="!dirty" :loading="saving" @click="save">保存</el-button>
|
||||
<el-button type="primary" v-hasPermi="['price:airBill:fee']" :disabled="!dirty" :loading="saving" @click="save">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { listAirBillFee, listAirBillFeeLog, saveAirBillFee } from '@/api/price/airBill'
|
||||
import { listAirBillFee, listAirBillFeeLog, saveAirBillFee, confirmAirBillFee, voidAirBillFee } from '@/api/price/airBill'
|
||||
import { listSurchargeOptions } from '@/api/price/surcharge'
|
||||
import { parseTime } from '@/utils/bms'
|
||||
|
||||
@@ -134,6 +136,7 @@ const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const logLoading = ref(false)
|
||||
const saving = ref(false)
|
||||
const acting = ref(false)
|
||||
const billCode = ref('')
|
||||
const rows = ref([])
|
||||
const deletedIds = ref([])
|
||||
@@ -172,6 +175,7 @@ function reset() {
|
||||
surchargeOptions.value = []
|
||||
surchargeLoading.value = false
|
||||
saving.value = false
|
||||
acting.value = false
|
||||
loading.value = false
|
||||
logLoading.value = false
|
||||
}
|
||||
@@ -307,6 +311,37 @@ function changeAlloc(row) {
|
||||
if (String(row.allocFlag) !== '1') row.allocDim = ''
|
||||
}
|
||||
|
||||
function locked(row) {
|
||||
return row?.billFeeStatus === '1' || row?.billFeeStatus === '3'
|
||||
}
|
||||
|
||||
function confirmRow(row) {
|
||||
if (!row?.id || acting.value) return
|
||||
acting.value = true
|
||||
confirmAirBillFee(billCode.value, row.id).then(res => {
|
||||
proxy.$modal.msgSuccess(res.msg || '费用已确定')
|
||||
load()
|
||||
}).finally(() => {
|
||||
acting.value = false
|
||||
})
|
||||
}
|
||||
|
||||
async function voidRow(row) {
|
||||
if (!row?.id || acting.value) return
|
||||
try {
|
||||
await proxy.$modal.confirm('确认作废这条费用吗?已分摊到订单的费用明细会逻辑删除,这条提单费用也会逻辑删除。')
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
acting.value = true
|
||||
voidAirBillFee(billCode.value, row.id).then(res => {
|
||||
proxy.$modal.msgSuccess(res.msg || '费用已作废')
|
||||
load()
|
||||
}).finally(() => {
|
||||
acting.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function removeRow(row) {
|
||||
if (row.id != null && !deletedIds.value.some(id => String(id) === String(row.id))) {
|
||||
deletedIds.value.push(row.id)
|
||||
@@ -404,15 +439,21 @@ function save() {
|
||||
}
|
||||
|
||||
function logAction(type) {
|
||||
return { 1: '新增', 2: '修改', 3: '删除' }[String(type)] || '维护'
|
||||
return { 1: '新增', 2: '修改', 3: '删除', 4: '确定费用', 5: '作废' }[String(type)] || '维护'
|
||||
}
|
||||
|
||||
function logType(type) {
|
||||
if (String(type) === '1') return 'success'
|
||||
if (String(type) === '3') return 'danger'
|
||||
const value = String(type)
|
||||
if (value === '1' || value === '4') return 'success'
|
||||
if (value === '3' || value === '5') return 'danger'
|
||||
return 'warning'
|
||||
}
|
||||
|
||||
function emptyText(row) {
|
||||
const value = String(row?.operationType)
|
||||
return value === '4' || value === '5' ? '无' : '空'
|
||||
}
|
||||
|
||||
function logTime(item) {
|
||||
return parseTime(item.createTime) || ''
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user