Files
rd_mes_uniapp/pages/wms/stock/addTransfer.vue

553 lines
16 KiB
Vue
Raw Normal View History

2025-12-18 14:11:48 +08:00
<template>
<view class="container">
<uni-forms :modelValue="formData" :label-width="60" label-align="right" class="form">
<uni-forms-item label="发起人">
<uni-easyinput v-model="formData.transferBy" placeholder="请输入发起人" />
</uni-forms-item>
<uni-forms-item label="运输员">
<uni-easyinput v-model="formData.transportBy" placeholder="请输入运输员" />
</uni-forms-item>
<uni-forms-item label="原库位">
<uni-easyinput v-model="formData.currentLocationCode" placeholder="请输入原库位条码" suffixIcon="scan"
@iconClick="scanCurrentLocationCode" @change="fetchLocationDetail" />
</uni-forms-item>
</uni-forms>
<uni-section class="section" title="库位库存明细" type="line">
<template #right>
<span v-if="materialList.length > 0">
库位
<strong style="margin: 0 2px">{{ materialList[0].storageLocationCode }}</strong>
,
<strong style="margin: 0 2px">{{ materialList.length }}</strong>
<!-- <checkbox @click="handleSelectAll" /> -->
</span>
</template>
<scroll-view scroll-y>
<checkbox-group @change="handleSelectRows">
<uni-card v-for="(item, index) in materialList" :key="index">
<template #title>
<view class="header">
<view class="matInfo">
<uni-tag :text="String(index + 1)" />
<text class="matName">
{{ item.materialName }}
</text>
</view>
<checkbox :value="String(index)" :checked="item.checked" @click="handleSelectItem(index)"
class="check-dot" />
</view>
</template>
<view class="card-list">
<view class="card-item">
<span class="card-item__label">物料编码</span>
<span class="card-item__value">{{ item.materialCode || '/' }}</span>
</view>
<view class="card-item">
<span class="card-item__label">批号</span>
<span class="card-item__value">{{ item.batchNo || '/' }}</span>
</view>
<view class="card-item">
<span class="card-item__label">箱号</span>
<span class="card-item__value">{{ item.lotNo || '/' }}</span>
</view>
<!-- <view class="card-item">
<span class="card-item__label">库位编码</span>
<span class="card-item__value">{{ item.storageLocationCode }}</span>
</view> -->
<view class="card-item">
<span class="card-item__label">入库时间</span>
<span class="card-item__value">{{ item.storageInTime || '/' }}</span>
</view>
</view>
<view class="card-data">
<view class="card-data-item">
<span class="card-data-item__label">库存总数</span>
<span class="card-data-item__value">{{ item.number || '/' }}</span>
</view>
<view class="card-data-item">
<span class="card-data-item__label">锁定数量</span>
<span class="card-data-item__value lock">{{ item.lockNumber || '/' }}</span>
</view>
<view class="card-data-item">
<span class="card-data-item__label">可用数量</span>
<span class="card-data-item__value avalible">{{ item.availableNumber || '/' }}</span>
</view>
</view>
<template #actions>
<u-transition :show="item.checked" mode="fade" duration="200">
<view class="card-actions">
<uni-forms-item label="目标库位" :labelWidth="80" name="targetLocation">
<uni-easyinput suffixIcon="scan" @iconClick="scanTargetLocationCode(index)"
@change="validateLocationCode(index)" type="text" v-model="item.targetLocation" />
</uni-forms-item>
<uni-forms-item label="转移数量" :labelWidth="80" name="actualNumber" style="margin-top: 10px">
<u-number-box button-size="36" inputWidth="100%" v-model="item.actualNumber" min="0"
:max="item.availableNumber"></u-number-box>
</uni-forms-item>
</view>
</u-transition>
</template>
</uni-card>
</checkbox-group>
</scroll-view>
</uni-section>
<u-modal v-if="showMultiFillModal" :show="showMultiFillModal" title="扫描库位编码" showCancelButton closeOnClickOverlay
@cancel="cancelMultiFill" @close="cancelMultiFill" :showConfirmButton="false">
<uni-easyinput suffixIcon="scan" @iconClick="scanMultiFill" v-model="multiTargetLocation"
@confirm="handleMultiFill" :focus="true" />
</u-modal>
<view class="btns">
<u-button @click="showMultiFill" class="sub-btn">批量移库</u-button>
<u-button type="primary" @click="submit" class="pri-btn">提交</u-button>
</view>
</view>
</template>
<script>
import { listStock, addTransfer } from '@/api/wms/stock.js';
import { listLocation } from '@/api/basic/location';
import { getConfigKey } from '@/api/system/config.js';
export default {
mounted() {
// 获取参数: 是否使用 货架-库位 查出完整库位
getConfigKey('wms.location.queryOption').then((res) => {
this.isShortLocation = Boolean(+res.msg);
});
},
data() {
return {
// 是否为短库位格式(货架-库位)
isShortLocation: false,
selectedRows: [],
showMultiFillModal: false,
multiTargetLocation: null,
formData: {
transferBy: null,
transportBy: null,
currentLocationCode: null,
currentWholeLocationCode: null
},
materialList: []
};
},
methods: {
// 原库位扫描事件
async scanCurrentLocationCode() {
this.$modal.scanCode(async (res) => {
if (!res.result) return;
this.$set(this.formData, 'currentLocationCode', res.result);
if (this.isShortLocation) {
this.formData.currentWholeLocationCode = await this.fetchWholeLocationCode(res.result);
} else {
this.formData.currentWholeLocationCode = this.formData.currentLocationCode;
}
});
},
// 原库位 change 事件,查询库存明细
async fetchLocationDetail(code) {
if (!code) return;
if (this.isShortLocation) {
this.formData.currentWholeLocationCode = await this.fetchWholeLocationCode(code);
} else {
this.formData.currentWholeLocationCode = this.formData.currentLocationCode;
}
this.fetchMaterialList();
},
// 目标库位扫描事件
async scanTargetLocationCode() {
this.$modal.scanCode(async (response) => {
if (!response.result) return;
let res;
if (this.isShortLocation) {
res = await this.fetchWholeLocationCode(response.result);
} else {
res = response.result;
}
if (res === this.formData.currentWholeLocationCode) {
this.$modal.msgError(`${i + 1} 条明细:原库位与目标库位相同`);
return;
}
if (res.split('-')[0] !== this.formData.currentWholeLocationCode.split('-')[0]) {
this.$modal.msgError(`${i + 1} 条明细:目标仓库与原仓库不一致`);
return;
}
this.materialList[i].targetLocation = response.result;
this.materialList[i].storageLocationBarcode = res;
});
},
// 目标库位 change 事件,校验库位是否合法
async validateLocationCode(i) {
if (!this.materialList[i].targetLocation) return;
let res;
if (this.isShortLocation) {
res = await this.fetchWholeLocationCode(this.materialList[i].targetLocation);
} else {
res = this.materialList[i].targetLocation;
}
if (res === this.formData.currentLocationCode) {
this.$modal.msgError(`${i + 1} 条明细:原库位与目标库位相同`);
this.materialList[i].targetLocation = null;
return;
}
if (res.split('-')[0] !== this.formData.currentWholeLocationCode.split('-')[0]) {
this.$modal.msgError(`${i + 1} 条明细:目标仓库与原仓库不一致`);
this.materialList[i].targetLocation = null;
return;
}
this.materialList[i].storageLocationBarcode = res;
},
// 获取完整库位
async fetchWholeLocationCode(shortLocationCode) {
if (!shortLocationCode) return null;
try {
this.$modal.loading('获取完整库位中...');
const storageShelvesCode = shortLocationCode.split('-')[0];
const storageLocationCode = shortLocationCode.split('-').slice(1).join('-');
if (!storageShelvesCode || !storageLocationCode) {
throw new Error(`库位编码格式不正确: ${shortLocationCode}`);
}
const res = await listLocation({
storageShelvesCode,
storageLocationCode
});
if (!res.total > 0) {
throw new Error(`未查询到 ${shortLocationCode} 的完整库位`);
} else if (res.total > 1) {
throw new Error(`查询到 ${shortLocationCode} 的完整库位有多个`);
}
return res.rows[0].storageLocationBarcode;
} catch (err) {
this.$modal.alert(err.message);
return null;
} finally {
this.$modal.closeLoading();
}
},
// 获取库存明细列表
async fetchMaterialList() {
this.$modal.loading('获取库存明细中...');
await listStock({
storageLocationCode: this.formData.currentLocationCode.split('-').slice(1).join('-'),
storageLocationBarcode: this.formData.currentWholeLocationCode
})
.then((res) => {
res.rows.forEach((item) => {
item.availableNumber = item.number - item.lockNumber;
});
this.materialList = res.rows;
})
.catch((err) => {
this.$modal.msgError(`查询明细失败\n${err}`);
});
this.$modal.closeLoading();
},
// 选中列表中某一项
handleSelectItem(i) {
this.$set(this.materialList[i], 'checked', !this.materialList[i].checked);
},
// 选中回调
handleSelectRows(e) {
this.selectedRows = e.target.value;
// for (let i = 0; i < this.materialList.length; i++) {
// this.$set(this.materialList[i], 'checked', Boolean(e.target.value.includes(String(i))));
// }
},
// 拆解库位
splitLocationCode(code) {
const parts = (code || '').split('-');
const whCode = parts[0] || null;
const areaCode = parts[1] || null;
const shelvesCode = parts[2] || null;
const storageLocationCode = parts.slice(3).join('-') || null;
return {
whCode,
areaCode,
shelvesCode,
storageLocationCode
};
},
// 批量填充扫码事件
scanMultiFill() {
this.$modal.scanCode(async (response) => {
let res;
if (this.isShortLocation) {
res = await this.fetchWholeLocationCode(response.result);
} else {
res = response.result;
}
if (res === this.formData.currentLocationCode) {
this.$modal.msgError(`${i + 1} 条明细:原库位与目标库位相同`);
return;
}
if (res.split('-')[0] !== this.formData.currentWholeLocationCode.split('-')[0]) {
this.$modal.msgError(`${i + 1} 条明细:目标仓库与原仓库不一致`);
return;
}
this.handleMultiFill(res);
});
},
// 批量填充选中项的目标库位
async handleMultiFill(code) {
console.log(code);
this.materialList.forEach(async (item) => {
if (item.checked) {
item.storageLocationBarcode = await this.fetchWholeLocationCode(code);
item.targetLocation = code;
}
});
this.showMultiFillModal = false;
},
// 打开批量填充
showMultiFill() {
if (!this.selectedRows.length > 0) {
this.$modal.msgError('请先选择明细');
return;
}
this.showMultiFillModal = true;
},
// 取消标签
cancelMultiFill() {
this.showMultiFillModal = false;
},
// 运输员
scanBarTransportBy() {
this.$modal.scanCode((res) => {
this.formData.transportBy = res.result;
});
},
// 发起人
scanBarTransferBy() {
this.$modal.scanCode((res) => {
this.formData.transferBy = res.result;
});
},
// 生成转移单明细
renderTransferData() {
const transferArr = [];
this.materialList.forEach((item) => {
if (item.checked) {
const { areaCode: targetAreaCode, shelvesCode: targetShelves, storageLocationCode: targetLocation } = this
.splitLocationCode(item.storageLocationBarcode);
const params = {
wmsMaterialStockId: item.id,
materialBatchNo: item.batchNo,
materialCode: item.materialCode,
materialName: item.materialName,
materialLotNo: item.lotNo,
originAreaCode: item.areaCode,
originShelvesCode: item.shelvesCode,
originLocationCode: item.storageLocationCode,
targetAreaCode,
targetShelves,
targetLocation,
availableNumber: item.availableNumber,
actualNumber: item.actualNumber,
specification1: item.specification,
texture: item.texture,
schemeNumber: null,
delStatus: null,
numUnitId: null,
remark: null
};
transferArr.push(params);
}
});
return transferArr;
},
submit() {
const _this = this;
this.materialList
.filter((item) => item.checked)
.forEach((item) => {
if (!item.id) {
console.log(item);
this.$modal.msg('存在未选择库存的明细!');
return;
} else if (item.actualNumber == null) {
this.$modal.msg('实际数量不能为空!');
return;
} else if (item.targetLocation == null) {
this.$modal.msg('目标库区不能为空!');
return;
}
});
uni.showModal({
title: '提示',
content: '您确定转移这些物料吗?',
success: function(res) {
if (res.confirm) {
const wmsStockTransferDetailList = _this.renderTransferData();
_this.$modal.loading('提交中...');
addTransfer({
warehouse: _this.formData.currentWholeLocationCode.split('-')[0],
//跳过审批流
transferStatus: '4',
transferBy: _this.formData.transferBy,
transportBy: _this.formData.transportBy,
delStatus: '0',
wmsStockTransferDetailList
}).then((res) => {
_this.$modal.closeLoading();
_this.$modal.msgSuccess('转移成功!');
setTimeout(() => {
_this.$tab.switchTab('/pages/work/index');
}, 500);
}).catch((err) => {
_this.$modal.alert('提交失败', err);
});
}
}
});
}
}
};
</script>
<style scoped lang="scss">
.container {
/* #ifdef H5 */
height: calc(100vh - 44px);
/* #endif */
/* #ifndef H5 */
height: 100vh;
/* #endif */
padding: 10px;
display: flex;
flex-direction: column;
.section {
flex: 1;
background: transparent;
overflow: hidden;
display: flex;
flex-direction: column;
::v-deep .uni-section-content {
flex: 1;
overflow: scroll;
}
}
.uni-card {
margin: 0 0 10px !important;
padding: 0 !important;
box-shadow: none !important;
border-radius: 11px;
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px;
border-bottom: 1px solid #d9d9d9;
.matInfo {
display: flex;
align-items: center;
.matName {
font-size: 16px;
font-weight: bold;
}
.uni-tag {
box-sizing: border-box;
height: 100%;
text-align: center;
margin-right: 8px;
aspect-ratio: 1;
}
}
.check-dot {
border-radius: 50%;
}
}
.card-list {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-gap: 4px;
.card-item {
display: flex;
flex-direction: column;
align-items: flex-start;
.card-item__label {
font-size: 13px;
}
.card-item__value {
font-weight: bold;
font-size: 15px;
color: #333;
}
}
}
.card-data {
display: flex;
margin-top: 10px;
background-color: #f1f1f1;
border-radius: 4px;
padding: 6px 0;
.card-data-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.card-data-item__label {
font-size: 13px;
}
.card-data-item__value {
font-weight: bold;
font-size: 15px;
color: #333;
&.lock {
color: #ef4444;
}
&.avalible {
color: #059669;
}
}
}
}
.card-actions {
padding: 8px;
border-top: 1px solid #d9d9d9;
.uni-forms-item {
margin-bottom: 0;
}
}
}
.btns {
display: flex;
justify-content: space-evenly;
align-items: center;
margin-top: 10px;
.sub-btn {
width: 47%;
}
.pri-btn {
width: 47%;
}
}
.u-popup {
position: absolute;
}
}
</style>