Files
bj_power/bj_power_wms/frontend/src/pages/Ledger.vue
T
SunYF 05b0b8a909 feat: 完成客户第12条需求+多系统改造
1. 新增预警表添加工单号字段,支持按工单号检索预警
2. 添加工单过滤在制品/绩效报表/预警中心查询
3. 补全WMS台账字段口径与权限配置
4. 修复备料单与数量不符处理流程
5. 新增配送进度/库位联动/自动出库功能
6. 修复AGV配送状态更新逻辑
7. 优化退料与库存管理细节
2026-09-19 17:04:58 +08:00

261 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
defineOptions({ name: 'Ledger' })
import { onMounted, reactive, ref } from 'vue'
import request from '../utils/request'
import { fmtTime } from '../utils/format'
import PageHelp from '../components/PageHelp.vue'
import { helpLedger } from '../help'
const orderNo = ref('')
const materialCode = ref('')
const materialName = ref('')
const stateFilter = ref('')
const completeRange = ref([]) // 完成时间范围 [start, end]
const loading = ref(false)
const queried = ref(false)
const rows = ref([])
const total = ref(0)
const page = reactive({ current: 1, size: 20 })
// 页签:备料台账 / 退料台账(并列)
const activeTab = ref('prep')
// 退料台账:return_orders(RECEIVED) 按工单+物料聚合的读模型,与备料台账并列,不回写已出库量
const retOrderNo = ref('')
const retMaterialCode = ref('')
const retMaterialName = ref('')
const retLoading = ref(false)
const retQueried = ref(false)
const retRows = ref([])
const retTotal = ref(0)
const retPage = reactive({ current: 1, size: 20 })
async function loadReturn() {
retLoading.value = true
try {
const data = await request.get('/return-order/ledger', {
params: {
orderNo: retOrderNo.value.trim() || undefined,
materialCode: retMaterialCode.value.trim() || undefined,
materialName: retMaterialName.value.trim() || undefined,
page: retPage.current,
pageSize: retPage.size
}
})
retRows.value = data?.list || []
retTotal.value = data?.total || 0
retQueried.value = true
} finally {
retLoading.value = false
}
}
function searchReturn() {
retPage.current = 1
loadReturn()
}
function onTabChange(name) {
if (name === 'return' && !retQueried.value) loadReturn()
}
// 缺口按净领用计算:净领用 = 已出库 − 已退回(退料自动回补缺口,退料后可再次补料)
function gapOf(row) {
const net = (row.outQty || 0) - (row.returnedQty || 0)
return Math.max((row.totalQty || 0) - net, 0)
}
async function load() {
loading.value = true
try {
const data = await request.get('/ledger/query', {
params: {
orderNo: orderNo.value.trim() || undefined,
materialCode: materialCode.value.trim() || undefined,
materialName: materialName.value.trim() || undefined,
state: stateFilter.value || undefined,
completeStart: completeRange.value?.[0] || undefined,
completeEnd: completeRange.value?.[1] || undefined,
page: page.current,
pageSize: page.size
}
})
rows.value = data?.list || []
total.value = data?.total || 0
queried.value = true
} finally {
loading.value = false
}
}
function search() {
page.current = 1
load()
}
onMounted(load)
// 备料四态(服务端派生,客户诉求 L242-258):齐套 / 预警 / 未齐套 / 未到货
function stateTagType(s) {
switch (s) {
case '齐套': return 'success'
case '预警': return 'danger'
case '未齐套': return 'warning'
case '未到货': return 'info'
default: return 'info'
}
}
function statusView(row) {
return row.state || '未齐套'
}
</script>
<template>
<div>
<el-tabs v-model="activeTab" @tab-change="onTabChange">
<el-tab-pane label="备料台账" name="prep">
<el-card shadow="never" class="mb12">
<el-form inline @submit.prevent="search">
<el-form-item label="工单号">
<el-input v-model="orderNo" placeholder="工单号(可空,查全部)" clearable
style="width:200px" @keyup.enter="search" />
</el-form-item>
<el-form-item label="图号">
<el-input v-model="materialCode" placeholder="图号(可空)" clearable
style="width:160px" @keyup.enter="search" />
</el-form-item>
<el-form-item label="物料名称">
<el-input v-model="materialName" placeholder="物料名称(可空)" clearable
style="width:160px" @keyup.enter="search" />
</el-form-item>
<el-form-item label="备料状态">
<el-select v-model="stateFilter" placeholder="全部" clearable style="width:130px" @change="search">
<el-option label="齐套" value="齐套" />
<el-option label="预警" value="预警" />
<el-option label="未齐套" value="未齐套" />
<el-option label="未到货" value="未到货" />
</el-select>
</el-form-item>
<el-form-item label="完成时间">
<el-date-picker v-model="completeRange" type="daterange" value-format="YYYY-MM-DD"
start-placeholder="开始" end-placeholder="结束" clearable
style="width:240px" @change="search" />
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="loading" @click="search">查询</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card v-if="queried" shadow="never">
<template #header>
工单备料台账 {{ total }}
</template>
<el-table :data="rows" border v-loading="loading" empty-text="暂无台账数据">
<el-table-column prop="id" label="ID" width="64" align="center" />
<el-table-column prop="orderNo" label="工单号" min-width="150" />
<el-table-column prop="materialCode" label="图号" min-width="140" />
<el-table-column prop="materialName" label="物料名称" min-width="160">
<template #default="{ row }">{{ row.materialName || '-' }}</template>
</el-table-column>
<el-table-column prop="totalQty" label="需求总量" width="100" align="center" />
<el-table-column prop="availableQty" label="可用库存" width="100" align="center">
<template #default="{ row }">{{ row.availableQty ?? 0 }}</template>
</el-table-column>
<el-table-column prop="outQty" label="已出库" width="90" align="center" />
<el-table-column prop="returnedQty" label="已退回" width="90" align="center" />
<el-table-column prop="consumedQty" label="已消耗" width="90" align="center" />
<el-table-column label="缺口" width="90" align="center">
<template #default="{ row }">{{ gapOf(row) }}</template>
</el-table-column>
<el-table-column label="目标工位" width="100" align="center">
<template #default="{ row }">{{ row.targetStation || '-' }}</template>
</el-table-column>
<el-table-column label="备料状态" width="110" align="center">
<template #default="{ row }">
<el-tag :type="stateTagType(statusView(row))" size="small">{{ statusView(row) }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="140">
<template #default="{ row }">{{ row.remark || '-' }}</template>
</el-table-column>
<el-table-column label="完成时间" width="165" align="center">
<template #default="{ row }">{{ row.completedAt ? fmtTime(row.completedAt) : '-' }}</template>
</el-table-column>
<el-table-column label="创建时间" width="165" align="center">
<template #default="{ row }">{{ fmtTime(row.createdAt) }}</template>
</el-table-column>
</el-table>
<div class="pager">
<el-pagination background layout="total, prev, pager, next" :total="total"
:current-page="page.current" :page-size="page.size"
@current-change="(p) => { page.current = p; load() }" />
</div>
</el-card>
<el-card v-else shadow="never">
<el-skeleton :rows="6" animated />
</el-card>
</el-tab-pane>
<el-tab-pane label="退料台账" name="return">
<el-card shadow="never" class="mb12">
<el-form inline @submit.prevent="searchReturn">
<el-form-item label="工单号">
<el-input v-model="retOrderNo" placeholder="工单号可空查全部" clearable
style="width:200px" @keyup.enter="searchReturn" />
</el-form-item>
<el-form-item label="图号">
<el-input v-model="retMaterialCode" placeholder="图号可空" clearable
style="width:160px" @keyup.enter="searchReturn" />
</el-form-item>
<el-form-item label="物料名称">
<el-input v-model="retMaterialName" placeholder="物料名称可空" clearable
style="width:160px" @keyup.enter="searchReturn" />
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="retLoading" @click="searchReturn">查询</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card v-if="retQueried" shadow="never">
<template #header>
工单退料台账(共 {{ retTotal }} 条)· 数据源:已收货退库单聚合,独立于备料台账、不回写已出库量
</template>
<el-table :data="retRows" border v-loading="retLoading" empty-text="暂无退料数据">
<el-table-column prop="orderNo" label="工单号" min-width="150">
<template #default="{ row }">{{ row.orderNo || '-' }}</template>
</el-table-column>
<el-table-column prop="materialCode" label="图号" min-width="140" />
<el-table-column prop="materialName" label="物料名称" min-width="160">
<template #default="{ row }">{{ row.materialName || '-' }}</template>
</el-table-column>
<el-table-column prop="spec" label="规格" min-width="120">
<template #default="{ row }">{{ row.spec || '-' }}</template>
</el-table-column>
<el-table-column prop="returnedQty" label="已退数量" width="100" align="center" />
<el-table-column prop="returnCount" label="退库笔数" width="100" align="center" />
<el-table-column label="退库工位" width="140" align="center">
<template #default="{ row }">{{ (row.stations && row.stations.length) ? row.stations.join('、') : '-' }}</template>
</el-table-column>
<el-table-column label="最近退库时间" width="165" align="center">
<template #default="{ row }">{{ row.lastReceive ? fmtTime(row.lastReceive) : '-' }}</template>
</el-table-column>
</el-table>
<div class="pager">
<el-pagination background layout="total, prev, pager, next" :total="retTotal"
:current-page="retPage.current" :page-size="retPage.size"
@current-change="(p) => { retPage.current = p; loadReturn() }" />
</div>
</el-card>
<el-card v-else shadow="never">
<el-skeleton :rows="6" animated />
</el-card>
</el-tab-pane>
</el-tabs>
</div>
<PageHelp :data="helpLedger" page="Ledger" />
</template>
<style scoped>
.mb12 { margin-bottom: 12px; }
.pager { display: flex; justify-content: flex-end; margin-top: 10px; }
</style>