库房回退

This commit is contained in:
2026-06-05 13:54:11 +08:00
parent 370142a99f
commit 3334248847
7 changed files with 181 additions and 158 deletions

View File

@@ -81,4 +81,9 @@ public class SysOaWarehouseTask extends BaseEntity {
private String unit;
/**
* 收货单 OSS ID逗号分隔按物料明细单独保存
*/
private String receiptDoc;
}

View File

@@ -89,4 +89,9 @@ public class SysOaWarehouseTaskBo extends BaseEntity {
* 单位
*/
private String unit;
/**
* 收货单 OSS IDCSV
*/
private String receiptDoc;
}

View File

@@ -91,4 +91,9 @@ public class SysOaWarehouseTaskVo {
*/
private String unit;
/**
* 收货单 OSS IDCSV按物料明细绑定
*/
private String receiptDoc;
}

View File

@@ -19,6 +19,7 @@
<result property="delFlag" column="del_flag"/>
<result property="remark" column="remark"/>
<result property="warehouseId" column="warehouse_id"/>
<result property="receiptDoc" column="receipt_doc"/>
</resultMap>

View File

@@ -91,14 +91,12 @@
type="primary"
icon="el-icon-shopping-cart-2"
size="mini"
@click="addDialogVisible = true"
@click="addWarehouseTask"
>新建采购单</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<add-purchase-dialog :visible.sync="addDialogVisible" @saved="getList" />
<el-table v-loading="loading" :data="oaWarehouseList" @selection-change="handleSelectionChange" :row-class-name="tableRowClassName">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" align="center" type="index"/>

View File

@@ -31,95 +31,10 @@
</el-form>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
<el-button type="primary" size="mini" icon="el-icon-plus" class="add-purchase-btn"
@click="addDialogVisible = true">新建采购单</el-button>
<add-purchase-dialog :visible.sync="addDialogVisible" @saved="getList" />
@click="$router.push('/oa/warehouse-data/addTask')">新建采购单</el-button>
<el-table v-loading="loading" :data="TaskList" @selection-change="handleSelectionChange" stripe size="small"
row-key="masterId" :expand-row-keys="expandedKeys"
:row-class-name="rowClassName" @expand-change="onRowExpand">
<el-table-column type="expand" width="36">
<template slot-scope="props">
<div style="padding: 10px 24px; background:#fafafa;">
<!-- 顶部模式 + 批量入库 -->
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:8px;">
<div style="display:flex; align-items:center; gap:8px;">
<span style="font-weight:600;">物料明细{{ (itemsMap[props.row.masterId] || []).length }} </span>
<el-radio-group v-model="mode" size="mini" v-if="props.row.status === 0">
<el-radio-button label="single">单个操作</el-radio-button>
<el-radio-button label="batch">批量操作</el-radio-button>
</el-radio-group>
</div>
<div v-if="mode === 'batch' && props.row.status === 0"
style="display:flex; align-items:center; gap:6px;">
<el-select v-model="batchStatus" size="mini" placeholder="批量设置状态" style="width:140px">
<el-option v-for="s in statusOptions" :key="s.value" :value="s.value" :label="s.label" />
</el-select>
<el-button size="mini" type="success" @click="submitComplete(props.row)">执行入库</el-button>
</div>
</div>
<!-- 收货单上传仅入库批量场景需要 -->
<div v-if="mode === 'batch' && props.row.status === 0" class="receipt-row">
<span class="r-label"><i class="el-icon-paperclip"></i> 收货单</span>
<file-upload v-model="props.row.receiptDoc" />
<span class="r-hint">提交时会一并保存到本单据</span>
</div>
<el-table v-loading="itemsLoading[props.row.masterId]"
:data="itemsMap[props.row.masterId] || []" size="mini" stripe ref="warehouseTable">
<el-table-column v-if="mode === 'batch' && props.row.status === 0"
type="selection" width="44" align="center" />
<el-table-column label="物料名" prop="name" min-width="120" />
<el-table-column label="截止" prop="endTime" width="120" align="center">
<template slot-scope="s">
<template v-if="s.row.endTime != null && s.row.taskStatus !== 2">
<span v-if="dayDiff(s.row.endTime) > 3">{{ parseTime(s.row.endTime, '{y}-{m}-{d}') }}</span>
<el-tag v-else-if="dayDiff(s.row.endTime) > 0" type="warning" size="mini" effect="plain">{{ dayDiff(s.row.endTime) }}</el-tag>
<el-tag v-else-if="dayDiff(s.row.endTime) === 0" type="danger" size="mini" effect="plain">今日</el-tag>
<el-tag v-else type="danger" size="mini" effect="plain">{{ Math.abs(dayDiff(s.row.endTime)) }}</el-tag>
</template>
</template>
</el-table-column>
<el-table-column label="数量" prop="taskInventory" width="64" align="right" />
<el-table-column label="单位" prop="unit" width="56" />
<el-table-column label="单价" width="110" align="right">
<template slot-scope="s">
<el-input v-if="s.row.taskStatus !== 2 && props.row.status === 0"
v-model="s.row.price" size="mini" type="number" placeholder="¥" />
<span v-else>¥{{ Number(s.row.price || 0).toFixed(2) }}</span>
</template>
</el-table-column>
<el-table-column label="型号" prop="model" min-width="100" />
<el-table-column label="规格" prop="specifications" min-width="100" />
<el-table-column label="品牌" prop="brand" min-width="80" />
<el-table-column label="备注" prop="remark" min-width="140">
<template slot-scope="s">
<el-input v-model="s.row.remark" size="mini" placeholder="备注"
:disabled="s.row.taskStatus === 2 || props.row.status === 1"
@blur="updateRemark(s.row)" />
</template>
</el-table-column>
<el-table-column label="状态" prop="taskStatus" width="110" align="center">
<template slot-scope="s">
<el-tag v-if="s.row.taskStatus === 2" type="success" size="mini">完成</el-tag>
<el-select v-else-if="mode === 'single' && props.row.status === 0"
v-model="s.row.taskStatus" size="mini" placeholder="状态"
@change="handleUpdateTask(s.row)">
<el-option v-for="opt in filteredStatusOptions(s.row)" :key="opt.value"
:value="opt.value" :label="opt.label" />
</el-select>
<el-tag v-else size="mini">{{ statusLabel(s.row.taskStatus) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="60">
<template slot-scope="s">
<el-button v-if="s.row.taskStatus !== 2 && props.row.status === 0"
size="mini" type="text" style="color:#f56c6c"
@click="handleBatchDelete(s.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
</el-table-column>
row-key="masterId" :row-class-name="rowClassName">
<el-table-column type="selection" width="44" align="center" />
<el-table-column label="操作时间" prop="signTime" width="100">
<template slot-scope="scope">{{ parseTime(scope.row.signTime, "{y}-{m}-{d}") }}</template>
@@ -147,10 +62,12 @@
@blur="updateMasterRemark(scope.row)" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="220" class-name="small-padding fixed-width">
<el-table-column label="操作" align="center" width="260" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-search"
@click="showDetail(scope.row)">查看</el-button>
<el-button size="mini" type="text" v-if="scope.row.status === 0"
@click="expandRow(scope.row)">执行入库</el-button>
@click="showDetail(scope.row)">执行入库</el-button>
<el-button size="mini" type="text" v-if="scope.row.status === 0"
@click="handComplete(scope.row)">完成</el-button>
<el-button size="mini" type="text" @click="handleExport(scope.row)">导出</el-button>
@@ -162,6 +79,93 @@
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
@pagination="getList" />
<!-- 物料明细抽屉 -->
<el-drawer size="70%"
:title="(searchItem.masterNum || parseTime(searchItem.signTime, '{y}-{m}-{d}')) + ' - 物料明细'"
:visible.sync="drawer">
<div style="padding: 0 16px 16px;">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:8px;">
<div style="display:flex; align-items:center; gap:8px;">
<span style="font-weight:600;">物料明细{{ warehouseTaskList.length }} </span>
<el-radio-group v-model="mode" size="mini" v-if="searchItem.status === 0">
<el-radio-button label="single">单个操作</el-radio-button>
<el-radio-button label="batch">批量操作</el-radio-button>
</el-radio-group>
</div>
<div v-if="mode === 'batch' && searchItem.status === 0"
style="display:flex; align-items:center; gap:6px;">
<el-select v-model="batchStatus" size="mini" placeholder="批量设置状态" style="width:140px">
<el-option v-for="s in statusOptions" :key="s.value" :value="s.value" :label="s.label" />
</el-select>
<el-button size="mini" type="success" @click="submitComplete">执行入库</el-button>
</div>
</div>
<el-table v-loading="loading" :data="warehouseTaskList" size="mini" stripe ref="warehouseTable">
<el-table-column v-if="mode === 'batch' && searchItem.status === 0"
type="selection" width="44" align="center" />
<el-table-column label="物料名" prop="name" min-width="120" />
<el-table-column label="截止" prop="endTime" width="120" align="center">
<template slot-scope="s">
<template v-if="s.row.endTime != null && s.row.taskStatus !== 2">
<span v-if="dayDiff(s.row.endTime) > 3">{{ parseTime(s.row.endTime, '{y}-{m}-{d}') }}</span>
<el-tag v-else-if="dayDiff(s.row.endTime) > 0" type="warning" size="mini" effect="plain">{{ dayDiff(s.row.endTime) }}</el-tag>
<el-tag v-else-if="dayDiff(s.row.endTime) === 0" type="danger" size="mini" effect="plain">今日</el-tag>
<el-tag v-else type="danger" size="mini" effect="plain">{{ Math.abs(dayDiff(s.row.endTime)) }}</el-tag>
</template>
</template>
</el-table-column>
<el-table-column label="数量" prop="taskInventory" width="64" align="right" />
<el-table-column label="单位" prop="unit" width="56" />
<el-table-column label="单价" width="110" align="right">
<template slot-scope="s">
<el-input v-if="s.row.taskStatus !== 2 && searchItem.status === 0"
v-model="s.row.price" size="mini" type="number" placeholder="¥" />
<span v-else>¥{{ Number(s.row.price || 0).toFixed(2) }}</span>
</template>
</el-table-column>
<el-table-column label="型号" prop="model" min-width="100" />
<el-table-column label="规格" prop="specifications" min-width="100" />
<el-table-column label="品牌" prop="brand" min-width="80" />
<el-table-column label="备注" prop="remark" min-width="140">
<template slot-scope="s">
<el-input v-model="s.row.remark" size="mini" placeholder="备注"
:disabled="s.row.taskStatus === 2 || searchItem.status === 1"
@blur="updateRemark(s.row)" />
</template>
</el-table-column>
<el-table-column label="收货单" width="240">
<template slot-scope="s">
<file-upload v-model="s.row.receiptDoc"
:limit="3"
:isShowTip="false"
:disabled="s.row.taskStatus === 2 || searchItem.status === 1"
class="cell-upload"
@input="onReceiptDocChange(s.row)" />
</template>
</el-table-column>
<el-table-column label="状态" prop="taskStatus" width="110" align="center">
<template slot-scope="s">
<el-tag v-if="s.row.taskStatus === 2" type="success" size="mini">完成</el-tag>
<el-select v-else-if="mode === 'single' && searchItem.status === 0"
v-model="s.row.taskStatus" size="mini" placeholder="状态"
@change="handleUpdateTask(s.row)">
<el-option v-for="opt in filteredStatusOptions(s.row)" :key="opt.value"
:value="opt.value" :label="opt.label" />
</el-select>
<el-tag v-else size="mini">{{ statusLabel(s.row.taskStatus) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="60">
<template slot-scope="s">
<el-button v-if="s.row.taskStatus !== 2 && searchItem.status === 0"
size="mini" type="text" style="color:#f56c6c"
@click="handleBatchDelete(s.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-drawer>
</div>
</template>
@@ -192,11 +196,6 @@ export default {
// 顶部状态筛选
statusFilter: 'all',
stat: { undone: 0, done: 0, urgent: 0 },
// 物料明细缓存(按 masterId
itemsMap: {},
itemsLoading: {},
// 当前展开的行(单展开)
expandedKeys: [],
// 新建采购单 dialog
addDialogVisible: false,
completeDrawer: false,
@@ -372,7 +371,7 @@ export default {
this.warehouseTaskList.splice(index, 1)
this.$message.success('删除成功')
},
submitComplete (masterRow) {
submitComplete () {
const rows = this.$refs.warehouseTable.selection || []
if (!rows.length) {
return this.$message.warning('请先勾选物料')
@@ -383,21 +382,21 @@ export default {
rows.forEach(r => { r.taskStatus = this.batchStatus })
// 1. 如果有收货单 → 先保存到 master
const saveMaster = (masterRow && masterRow.receiptDoc)
? updateOaWarehouseMaster({
masterId: masterRow.masterId,
receiptDoc: masterRow.receiptDoc,
type: masterRow.type
})
: Promise.resolve()
saveMaster.then(() => updateOaWarehouseTaskBatch(rows)).then(() => {
updateOaWarehouseTaskBatch(rows).then(() => {
this.getList()
this.drawer = false
this.$message.success(`已批量入库 ${rows.length}`)
})
},
// 单条收货单上传后立刻保存
onReceiptDocChange (row) {
updateOaWarehouseTask({
taskId: row.taskId,
receiptDoc: row.receiptDoc
}).then(() => {
this.$message.success('收货单已保存')
})
},
/** 执行入库操作 */
handleIn (row) {
// 更新采购单情况
@@ -443,8 +442,6 @@ export default {
this.TaskList = res.rows || [];
this.total = res.total || 0;
this.loading = false;
// 重置已展开行的缓存
this.itemsMap = {};
});
this.refreshStat();
},
@@ -485,40 +482,6 @@ export default {
this.queryParams.pageNum = 1
this.getList()
},
// 行展开:懒加载物料明细 + 同步 warehouseTaskList兼容老方法
onRowExpand (row, expanded) {
// 单展开:只保留当前
if (expanded && expanded.length) {
this.expandedKeys = [row.masterId]
} else {
this.expandedKeys = this.expandedKeys.filter(k => k !== row.masterId)
}
if (!expanded || !expanded.length) return
const id = row.masterId
this.currentMasterId = id
if (this.itemsMap[id]) {
this.warehouseTaskList = this.itemsMap[id]
return
}
this.$set(this.itemsLoading, id, true)
getOaWarehouseTaskByMasterId(id).then(res => {
const list = res.data || res.rows || []
this.$set(this.itemsMap, id, list)
this.warehouseTaskList = list
}).finally(() => {
this.$set(this.itemsLoading, id, false)
})
},
// 程序化展开(用于"执行入库"按钮)
expandRow (row) {
if (this.expandedKeys.includes(row.masterId)) {
this.expandedKeys = this.expandedKeys.filter(k => k !== row.masterId)
return
}
this.expandedKeys = [row.masterId]
// 触发数据加载
this.onRowExpand(row, [row.masterId])
},
// 取消按钮
cancel () {
this.open = false;
@@ -683,20 +646,61 @@ export default {
border-radius: 4px;
i { font-size: 11px; margin-right: 2px; }
}
.receipt-row {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 0;
margin-bottom: 6px;
border-bottom: 1px dashed #ebeef5;
.r-label {
color: #606266;
font-size: 12px;
flex-shrink: 0;
i { margin-right: 2px; color: #909399; }
// 表格里的紧凑上传按钮 / 文件列表
.cell-upload {
::v-deep .upload-file-uploader { margin-bottom: 4px; }
::v-deep .el-upload .el-button {
padding: 4px 8px !important;
font-size: 12px !important;
height: 24px;
line-height: 14px;
}
// 已上传文件列表
::v-deep .upload-file-list {
margin: 0;
padding: 0;
.el-upload-list__item {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: nowrap;
height: 24px;
line-height: 22px;
margin: 2px 0 0 0 !important;
padding: 0 6px;
border: 1px solid #ebeef5;
border-radius: 4px;
background: #fafafa;
// 文件名:超出省略
.el-icon-document {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 12px;
color: #303133;
padding-right: 6px;
}
// 操作按钮:纯图标,不换行
.ele-upload-list__item-content-action {
flex-shrink: 0;
display: inline-flex;
align-items: center;
gap: 2px;
.el-button {
padding: 0 4px !important;
margin: 0 !important;
height: 20px;
line-height: 20px;
font-size: 0; // 隐藏文字
[class^="el-icon-"] {
font-size: 14px;
margin: 0;
}
}
}
}
}
.r-hint { color: #909399; font-size: 11px; }
::v-deep .el-upload-list { font-size: 12px; }
}
</style>

View File

@@ -0,0 +1,5 @@
-- 物料明细加"收货单"字段CSV 形式的 sys_oss 主键
-- 让收货单按物料明细行单独绑定,而不是绑定到整张采购单
ALTER TABLE `sys_oa_warehouse_task`
ADD COLUMN `receipt_doc` varchar(500) DEFAULT NULL COMMENT '收货单 OSS ID逗号分隔sys_oss.oss_id'
AFTER `remark`;