|
|
|
@@ -4,6 +4,7 @@ import { Plus } from '@lucide/vue'
|
|
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
|
|
|
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
|
|
|
|
import { listSuppliers, type Supplier } from '@/modules/suppliers'
|
|
|
|
|
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
|
|
|
|
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
|
|
|
|
import AppDialog from '@/shared/components/AppDialog.vue'
|
|
|
|
@@ -33,8 +34,9 @@ const loadError = ref<ApiErrorDetails | null>(null)
|
|
|
|
|
const mutationError = ref<ApiErrorDetails | null>(null)
|
|
|
|
|
const activeTab = ref('basic')
|
|
|
|
|
const feeDialogOpen = ref(false)
|
|
|
|
|
const feeForm = reactive({ item: '', currency: 'USD', amount: 0, status: 'PENDING_REVIEW', description: '' })
|
|
|
|
|
const feeItemOptions = ['仓储费', '入库操作费', '出库操作费', '换标费', '托盘费', '派送费', '转仓费', '异常处理费', '其他']
|
|
|
|
|
const supplierOptions = ref<Supplier[]>([])
|
|
|
|
|
const feeForm = reactive({ itemCode: '', itemName: '', direction: 'RECEIVABLE' as 'RECEIVABLE' | 'PAYABLE', currency: 'USD' as 'CNY' | 'USD', amount: 0, businessDate: new Date().toISOString().slice(0, 10), supplierId: '', quantity: undefined as number | undefined, quantityUnit: '', periodStart: '', periodEnd: '' })
|
|
|
|
|
const feeItemOptions = [{ code: 'STORAGE_FEE', name: '仓租' }, { code: 'INBOUND_OPERATION', name: '入库操作费' }, { code: 'OUTBOUND_OPERATION', name: '出库操作费' }, { code: 'RELABEL_FEE', name: '换标费' }, { code: 'REBOX_FEE', name: '换箱费' }, { code: 'PALLET_FEE', name: '托盘费' }, { code: 'DELIVERY_FEE', name: '派送费' }, { code: 'TRANSFER_FEE', name: '转仓费' }, { code: 'EXCEPTION_FEE', name: '异常处理费' }, { code: 'OTHER', name: '其他' }]
|
|
|
|
|
|
|
|
|
|
function text(value: unknown) { return String(value ?? '').trim() || '—' }
|
|
|
|
|
function yesNo(value: boolean | null | undefined) { return value == null ? '—' : value ? '是' : '否' }
|
|
|
|
@@ -93,8 +95,8 @@ const cargo = computed<CargoRow[]>(() => (detail.value?.cargo || []).map((item)
|
|
|
|
|
cartons: Number(item.packageCount || 0), quantity: Number(item.productQuantity || 0), weight: Number(item.grossWeightKg || 0), volume: Number(item.volumeCbm || 0),
|
|
|
|
|
})))
|
|
|
|
|
const fees = computed<FeeRow[]>(() => (detail.value?.fees || []).map((item) => ({
|
|
|
|
|
id: String(item.id), item: item.feeItem, currency: item.currency, amount: `${item.currency} ${Number(item.amount).toFixed(2)}`,
|
|
|
|
|
status: feeStatusLabel(item.status), description: text(item.supplierDescription),
|
|
|
|
|
id: String(item.id), item: item.feeItemName, currency: item.originalCurrency, amount: `${item.originalCurrency} ${Number(item.originalAmount).toFixed(2)}`,
|
|
|
|
|
status: feeStatusLabel(item.reviewStatus === 'PENDING' ? 'PENDING_REVIEW' : item.reviewStatus), description: text(item.supplierName || (item.chargeableQuantity == null ? null : `${item.chargeableQuantity} ${item.quantityUnit || ''}`)),
|
|
|
|
|
})))
|
|
|
|
|
const logs = computed<LogRow[]>(() => {
|
|
|
|
|
const statusRows = (detail.value?.statusHistory || []).map((item) => ({
|
|
|
|
@@ -134,16 +136,22 @@ async function load() {
|
|
|
|
|
catch (error) { loadError.value = error instanceof Error && error.message === '当前环境未签发海外访问令牌' ? { message: error.message } : getApiErrorDetails(error, '尾程订单详情加载失败,请重试') }
|
|
|
|
|
finally { loading.value = false }
|
|
|
|
|
}
|
|
|
|
|
function openFeeDialog() {
|
|
|
|
|
Object.assign(feeForm, { item: '', currency: 'USD', amount: 0, status: 'PENDING_REVIEW', description: '' })
|
|
|
|
|
async function openFeeDialog() {
|
|
|
|
|
Object.assign(feeForm, { itemCode: '', itemName: '', direction: 'RECEIVABLE', currency: 'USD', amount: 0, businessDate: new Date().toISOString().slice(0, 10), supplierId: '', quantity: undefined, quantityUnit: '', periodStart: '', periodEnd: '' })
|
|
|
|
|
mutationError.value = null; feeDialogOpen.value = true
|
|
|
|
|
if (access.can('platform:supplier:list') && supplierOptions.value.length === 0) {
|
|
|
|
|
try { supplierOptions.value = (await listSuppliers({ page: 1, size: 200, status: 'ACTIVE' })).records.filter((item) => item.status === 'ACTIVE') }
|
|
|
|
|
catch (error) { mutationError.value = getApiErrorDetails(error, '供应商列表加载失败') }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
async function saveFee() {
|
|
|
|
|
if (!feeForm.item.trim() || feeForm.amount <= 0) { ElMessage.warning('请填写费用项目和大于 0 的金额'); return }
|
|
|
|
|
if (!feeForm.itemCode || !feeForm.itemName.trim() || feeForm.amount <= 0 || !feeForm.businessDate) { ElMessage.warning('请填写费用项目、业务日期和大于 0 的金额'); return }
|
|
|
|
|
const supplier = supplierOptions.value.find((item) => String(item.id) === feeForm.supplierId)
|
|
|
|
|
if (feeForm.direction === 'PAYABLE' && !supplier) { ElMessage.warning('应付费用必须选择供应商'); return }
|
|
|
|
|
saving.value = true; mutationError.value = null
|
|
|
|
|
try {
|
|
|
|
|
await createLastMileOrderFee(orderId.value, { feeItem: feeForm.item.trim(), currency: feeForm.currency, amount: feeForm.amount, status: feeForm.status, supplierDescription: feeForm.description.trim() || undefined })
|
|
|
|
|
feeDialogOpen.value = false; ElMessage.success('费用已新增'); await load(); activeTab.value = 'fees'
|
|
|
|
|
await createLastMileOrderFee(orderId.value, { feeItemCode: feeForm.itemCode, feeItemName: feeForm.itemName.trim(), direction: feeForm.direction, currency: feeForm.currency, amount: feeForm.amount, businessDate: feeForm.businessDate, supplierId: supplier ? String(supplier.id) : undefined, supplierName: supplier?.name, supplierCategory: supplier?.category, settlementMode: supplier ? 'ON_ACCOUNT' : undefined, chargeableQuantity: feeForm.quantity, quantityUnit: feeForm.quantity == null ? undefined : feeForm.quantityUnit.trim(), unitPrice: feeForm.quantity ? feeForm.amount / feeForm.quantity : undefined, periodStart: feeForm.periodStart || undefined, periodEnd: feeForm.periodEnd || undefined })
|
|
|
|
|
feeDialogOpen.value = false; ElMessage.success('费用已提交财务审核'); await load(); activeTab.value = 'fees'
|
|
|
|
|
} catch (error) { mutationError.value = getApiErrorDetails(error, '费用新增失败,请重试') }
|
|
|
|
|
finally { saving.value = false }
|
|
|
|
|
}
|
|
|
|
@@ -175,11 +183,14 @@ onMounted(load)
|
|
|
|
|
</section>
|
|
|
|
|
<AppDialog v-model="feeDialogOpen" title="新增费用" width="min(720px, calc(100vw - 28px))" confirm-text="保存费用" :confirm-loading="saving" @confirm="saveFee">
|
|
|
|
|
<el-form class="fee-form" label-position="top"><div class="fee-form-grid">
|
|
|
|
|
<el-form-item label="费用项目" required><el-select v-model="feeForm.item" filterable allow-create clearable default-first-option placeholder="选择或输入费用项目"><el-option v-for="item in feeItemOptions" :key="item" :label="item" :value="item" /></el-select></el-form-item>
|
|
|
|
|
<el-form-item label="供应商/说明"><el-input v-model="feeForm.description" maxlength="500" placeholder="请输入供应商或费用说明" /></el-form-item>
|
|
|
|
|
<el-form-item label="币种" required><el-select v-model="feeForm.currency" placeholder="请选择币种"><el-option v-for="currency in ['USD', 'CNY', 'EUR', 'GBP', 'CAD', 'MXN']" :key="currency" :label="currency" :value="currency" /></el-select></el-form-item>
|
|
|
|
|
<el-form-item label="费用项目" required><el-select v-model="feeForm.itemCode" filterable placeholder="请选择费用项目" @change="feeForm.itemName = feeItemOptions.find(item => item.code === feeForm.itemCode)?.name || ''"><el-option v-for="item in feeItemOptions" :key="item.code" :label="item.name" :value="item.code" /></el-select></el-form-item>
|
|
|
|
|
<el-form-item label="收付方向" required><el-select v-model="feeForm.direction"><el-option label="应收客户" value="RECEIVABLE" /><el-option label="应付供应商" value="PAYABLE" /></el-select></el-form-item>
|
|
|
|
|
<el-form-item v-if="feeForm.direction === 'PAYABLE'" label="供应商" required><el-select v-model="feeForm.supplierId" filterable placeholder="请选择供应商"><el-option v-for="item in supplierOptions" :key="String(item.id)" :label="item.name" :value="String(item.id)" /></el-select></el-form-item>
|
|
|
|
|
<el-form-item label="业务日期" required><el-date-picker v-model="feeForm.businessDate" type="date" value-format="YYYY-MM-DD" /></el-form-item>
|
|
|
|
|
<el-form-item label="币种" required><el-select v-model="feeForm.currency" placeholder="请选择币种"><el-option label="USD" value="USD" /><el-option label="CNY" value="CNY" /></el-select></el-form-item>
|
|
|
|
|
<el-form-item label="金额" required><el-input-number v-model="feeForm.amount" :min="0" :precision="2" :step="10" controls-position="right" placeholder="请输入费用金额" /></el-form-item>
|
|
|
|
|
<el-form-item class="fee-form-full" label="状态" required><el-select v-model="feeForm.status" placeholder="请选择费用状态"><el-option label="待审核" value="PENDING_REVIEW" /><el-option label="审核通过" value="APPROVED" /><el-option label="已驳回" value="REJECTED" /></el-select></el-form-item>
|
|
|
|
|
<el-form-item label="计费数量"><el-input-number v-model="feeForm.quantity" :min="0.0001" :precision="4" controls-position="right" /></el-form-item><el-form-item label="计费单位"><el-input v-model="feeForm.quantityUnit" :disabled="feeForm.quantity == null" placeholder="如 箱、托、天、m³" /></el-form-item>
|
|
|
|
|
<el-form-item label="计费开始日期"><el-date-picker v-model="feeForm.periodStart" type="date" value-format="YYYY-MM-DD" placeholder="仓租等周期费用填写" /></el-form-item><el-form-item label="计费结束日期"><el-date-picker v-model="feeForm.periodEnd" type="date" value-format="YYYY-MM-DD" placeholder="仓租等周期费用填写" /></el-form-item>
|
|
|
|
|
</div></el-form>
|
|
|
|
|
</AppDialog>
|
|
|
|
|
</OverseasDetailShell>
|
|
|
|
|