factory_web/src/views/mpr/material_requisition_form.vue

398 lines
14 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.

<template>
<el-container>
<el-main class="nopadding">
<div style="padding: 20px;">
<el-descriptions title="物资领用单" :column="3" border>
<el-descriptions-item label="编号">{{ formData.number || '自动生成' }}</el-descriptions-item>
<el-descriptions-item label="领用部门">{{ formData.belong_dept_name || currentUser.belong_dept_name }}</el-descriptions-item>
<el-descriptions-item label="填报时间">
<el-date-picker
v-if="isEditable"
v-model="formData.req_date"
type="date"
placeholder="填报时间"
value-format="YYYY-MM-DD"
style="width: 200px;"
/>
<span v-else>{{ formData.req_date }}</span>
</el-descriptions-item>
<el-descriptions-item label="操作人">{{ formData.create_by_name || currentUser.name }}</el-descriptions-item>
<el-descriptions-item label="领取人">
<el-input v-if="isEditable" v-model="formData.collector" placeholder="领取人" style="width: 200px;" />
<span v-else>{{ formData.collector }}</span>
</el-descriptions-item>
<el-descriptions-item label="备注">
<el-input v-if="isEditable" v-model="formData.note" type="textarea" :rows="1" placeholder="备注" />
<span v-else>{{ formData.note }}</span>
</el-descriptions-item>
</el-descriptions>
<!-- 库存物品(从物料库存中选择) -->
<div style="margin-top: 20px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<h4 style="margin: 0;">库存物品领用</h4>
<el-button v-if="isEditable" type="primary" size="small" icon="el-icon-plus" @click="openStockPicker">从库存选择</el-button>
</div>
<el-table :data="stockItems" border stripe style="width: 100%">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column label="领用类型" width="120">
<template #default="scope">
<el-input v-if="isEditable" v-model="scope.row.req_type" placeholder="领用类型" />
<span v-else>{{ scope.row.req_type }}</span>
</template>
</el-table-column>
<el-table-column label="物资名称" min-width="120" prop="name" show-overflow-tooltip></el-table-column>
<el-table-column label="规格型号" width="120" prop="spec" show-overflow-tooltip></el-table-column>
<el-table-column label="单位" width="80" prop="unit"></el-table-column>
<el-table-column label="库存量" width="100">
<template #default="scope">
<span style="color: #909399;">{{ scope.row.stock_quantity }}</span>
</template>
</el-table-column>
<el-table-column label="领用量" width="130">
<template #default="scope">
<el-input-number
v-if="isEditable"
v-model="scope.row.quantity"
:min="0"
:max="Number(scope.row.stock_quantity)"
:precision="3"
size="small"
controls-position="right"
/>
<span v-else>{{ scope.row.quantity }}</span>
</template>
</el-table-column>
<el-table-column label="备注" min-width="120">
<template #default="scope">
<el-input v-if="isEditable" v-model="scope.row.note" placeholder="备注" />
<span v-else>{{ scope.row.note }}</span>
</template>
</el-table-column>
<el-table-column v-if="isEditable" label="操作" width="80" align="center" fixed="right">
<template #default="scope">
<el-button link type="danger" @click="delStockItem(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 非物资清单物品 -->
<div style="margin-top: 20px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<h4 style="margin: 0;">非物资清单物品</h4>
<el-button v-if="isEditable" type="primary" size="small" icon="el-icon-plus" @click="addNonStockItem">添加</el-button>
</div>
<el-table :data="nonStockItems" border stripe style="width: 100%">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column label="领用类型" width="120">
<template #default="scope">
<el-input v-if="isEditable" v-model="scope.row.req_type" placeholder="领用类型" />
<span v-else>{{ scope.row.req_type }}</span>
</template>
</el-table-column>
<el-table-column label="物资名称" min-width="120">
<template #default="scope">
<el-input v-if="isEditable" v-model="scope.row.name" placeholder="名称" />
<span v-else>{{ scope.row.name }}</span>
</template>
</el-table-column>
<el-table-column label="规格型号" width="120">
<template #default="scope">
<el-input v-if="isEditable" v-model="scope.row.spec" placeholder="规格型号" />
<span v-else>{{ scope.row.spec }}</span>
</template>
</el-table-column>
<el-table-column label="单位" width="80">
<template #default="scope">
<el-input v-if="isEditable" v-model="scope.row.unit" placeholder="单位" />
<span v-else>{{ scope.row.unit }}</span>
</template>
</el-table-column>
<el-table-column label="领用量" width="130">
<template #default="scope">
<el-input-number v-if="isEditable" v-model="scope.row.quantity" :min="0" :precision="3" size="small" controls-position="right" />
<span v-else>{{ scope.row.quantity }}</span>
</template>
</el-table-column>
<el-table-column label="备注" min-width="120">
<template #default="scope">
<el-input v-if="isEditable" v-model="scope.row.note" placeholder="备注" />
<span v-else>{{ scope.row.note }}</span>
</template>
</el-table-column>
<el-table-column v-if="isEditable" label="操作" width="80" align="center" fixed="right">
<template #default="scope">
<el-button link type="danger" @click="delNonStockItem(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div style="text-align: right; margin-top: 15px; font-size: 14px;">
<span style="margin-right: 30px;">部门负责人___________</span>
<span>领取人:{{ formData.collector || '___________' }}</span>
</div>
</div>
<el-footer style="padding: 10px 20px; text-align: left;">
<el-button type="danger"
v-if="localMode === 'edit'"
style="margin-right: 4px;"
@click="handleDel"
:loading="saveLoading"
>删除</el-button>
<el-button
v-if="isEditable"
type="primary"
style="margin-right: 4px;"
@click="handleSubmit"
:loading="saveLoading"
>提交审批</el-button>
<ticketd_b
v-if="formData.ticket_ && localMode === 'show'"
:t_id="formData.id"
:ticket_="formData.ticket_"
:ticket_data="ticket_data"
@success="$emit('success')"
ref="ticketd_b"
></ticketd_b>
</el-footer>
</el-main>
<el-aside width="20%" v-if="formData.ticket_">
<ticketd :ticket_="formData.ticket_" @success="$emit('success')"></ticketd>
</el-aside>
</el-container>
<!-- 库存选择对话框 -->
<el-dialog title="选择库存物品" v-model="stockPickerVisible" width="80%" destroy-on-close>
<div style="margin-bottom: 10px; display: flex; gap: 10px;">
<el-input v-model="stockSearch" placeholder="搜索名称/规格/供应商" clearable style="width: 300px;" @keyup.enter="loadStockList"></el-input>
<el-button type="primary" @click="loadStockList">搜索</el-button>
</div>
<el-table :data="stockList" border stripe @selection-change="handleStockSelectionChange" max-height="400">
<el-table-column type="selection" width="55" />
<el-table-column label="名称" prop="name" min-width="120" show-overflow-tooltip></el-table-column>
<el-table-column label="规格" prop="spec" width="120" show-overflow-tooltip></el-table-column>
<el-table-column label="单位" prop="unit" width="80"></el-table-column>
<el-table-column label="库存量" prop="quantity" width="100"></el-table-column>
<el-table-column label="仓库" prop="warehouse_name" width="100" show-overflow-tooltip></el-table-column>
<el-table-column label="入库单号" prop="entry_number" width="180" show-overflow-tooltip></el-table-column>
<el-table-column label="供应商" prop="supplier_name" width="120" show-overflow-tooltip></el-table-column>
</el-table>
<template #footer>
<el-button @click="stockPickerVisible = false">取消</el-button>
<el-button type="primary" @click="confirmStockPick">确认选择 ({{ selectedStocks.length }})</el-button>
</template>
</el-dialog>
</template>
<script>
import ticketd_b from "@/views/wf/ticketd_b.vue"
import ticketd from '@/views/wf/ticketd.vue'
export default {
name: 'MaterialRequisitionForm',
components: { ticketd_b, ticketd },
props: {
mode: { type: String, default: 'show' },
t_id: { type: String, default: "" }
},
emits: ['success'],
data() {
return {
formData: {},
stockItems: [],
nonStockItems: [],
localMode: this.mode,
saveLoading: false,
ticket_data: {},
currentUser: this.$TOOL.data.get("USER_INFO") || {},
stockPickerVisible: false,
stockSearch: '',
stockList: [],
selectedStocks: [],
}
},
computed: {
isEditable() {
return this.localMode === 'add' || this.localMode === 'edit'
},
},
async mounted() {
if (this.t_id) {
this.loadData()
} else {
this.localMode = 'add'
}
},
methods: {
async loadData() {
try {
let res = await this.$API.mpr.materialRequisition.item.req(this.t_id)
this.formData = res
const allItems = res.items_ || []
this.stockItems = allItems.filter(i => i.is_stock_item).map(i => ({
...i,
stock_id: i.stock,
stock_quantity: '—',
}))
this.nonStockItems = allItems.filter(i => !i.is_stock_item)
if (res.ticket_ && res.ticket_.state_.type === 1 && res.create_by === this.currentUser.id) {
this.localMode = 'edit'
}
} catch (error) {
this.$message.error('加载失败')
}
},
async openStockPicker() {
this.stockSearch = ''
this.selectedStocks = []
await this.loadStockList()
this.stockPickerVisible = true
},
async loadStockList() {
try {
let res = await this.$API.mpr.warehouseStock.list.req({ page: 0, search: this.stockSearch })
const existingIds = new Set(this.stockItems.map(i => i.stock_id))
this.stockList = (Array.isArray(res) ? res : res.results || []).filter(
s => Number(s.quantity) > 0 && !existingIds.has(s.id)
)
} catch (error) {
this.$message.error('加载库存失败')
}
},
handleStockSelectionChange(rows) {
this.selectedStocks = rows
},
confirmStockPick() {
this.selectedStocks.forEach(s => {
this.stockItems.push({
is_stock_item: true,
stock_id: s.id,
name: s.name,
spec: s.spec,
unit: s.unit,
stock_quantity: s.quantity,
quantity: 0,
req_type: '',
note: '',
})
})
this.stockPickerVisible = false
},
addNonStockItem() {
this.nonStockItems.push({
is_stock_item: false,
name: '',
spec: '',
unit: '',
quantity: 0,
req_type: '',
note: '',
})
},
delStockItem(index) {
this.stockItems.splice(index, 1)
},
delNonStockItem(index) {
this.nonStockItems.splice(index, 1)
},
validate() {
if (!this.formData.req_date) {
this.$message.warning('请选择填报时间')
return false
}
const allItems = [...this.stockItems, ...this.nonStockItems]
if (allItems.length === 0) {
this.$message.warning('请至少添加一项领用物品')
return false
}
for (let i = 0; i < this.stockItems.length; i++) {
const row = this.stockItems[i]
if (!row.quantity || Number(row.quantity) <= 0) {
this.$message.warning(`库存物品第${i + 1}领用量必须大于0`)
return false
}
if (Number(row.quantity) > Number(row.stock_quantity)) {
this.$message.warning(`库存物品第${i + 1}行:领用量(${row.quantity})超过库存量(${row.stock_quantity})`)
return false
}
}
for (let i = 0; i < this.nonStockItems.length; i++) {
const row = this.nonStockItems[i]
if (!row.name) {
this.$message.warning(`非清单物品第${i + 1}行:请填写物资名称`)
return false
}
if (!row.quantity || Number(row.quantity) <= 0) {
this.$message.warning(`非清单物品第${i + 1}领用量必须大于0`)
return false
}
}
return true
},
buildPayload() {
const items = []
this.stockItems.forEach(row => {
items.push({
is_stock_item: true,
stock: row.stock_id,
req_type: row.req_type,
name: row.name,
spec: row.spec,
unit: row.unit,
quantity: row.quantity,
note: row.note,
})
})
this.nonStockItems.forEach(row => {
items.push({
is_stock_item: false,
req_type: row.req_type,
name: row.name,
spec: row.spec,
unit: row.unit,
quantity: row.quantity,
note: row.note,
})
})
return {
req_date: this.formData.req_date,
collector: this.formData.collector,
note: this.formData.note,
items: items
}
},
async handleSubmit() {
if (!this.validate()) return
this.saveLoading = true
try {
if (this.localMode === 'add') {
await this.$API.mpr.materialRequisition.create.req(this.buildPayload())
this.$message.success('提交成功')
} else if (this.localMode === 'edit') {
await this.$API.mpr.materialRequisition.update.req(this.formData.id, this.buildPayload())
this.$message.success('更新并提交成功')
}
this.$emit('success')
} catch (error) {
const msg = error?.response?.data?.detail || error?.response?.data?.message || '提交失败'
this.$message.error(msg)
} finally {
this.saveLoading = false
}
},
handleDel() {
this.$confirm('确定删除该领用单吗?', '提示', { type: 'warning' })
.then(() => {
this.$API.mpr.materialRequisition.delete.req(this.formData.id).then(() => {
this.$message.success('删除成功')
this.$emit('success')
})
})
}
}
}
</script>