前后端修改

This commit is contained in:
2025-11-25 01:13:26 +08:00
parent f0a1598fa3
commit 28d0852a81
18 changed files with 1574 additions and 440 deletions

View File

@@ -9,6 +9,15 @@ export function listActualWarehouse(query) {
})
}
// 获取完整三级目录
export function listActualWarehouseTree(query) {
return request({
url: '/wms/actualWarehouse/tree',
method: 'get',
params: query
})
}
// 查询实际库区/库位自关联详细
export function getActualWarehouse(actualWarehouseId) {
return request({
@@ -26,6 +35,25 @@ export function addActualWarehouse(data) {
})
}
// 导入实际库区/库位
export function importActualWarehouse(data) {
return request({
url: '/wms/actualWarehouse/importData',
method: 'post',
data,
headers: { 'Content-Type': 'multipart/form-data' }
})
}
// 批量新增三级目录
export function createActualWarehouseHierarchy(data) {
return request({
url: '/wms/actualWarehouse/hierarchy',
method: 'post',
data
})
}
// 修改实际库区/库位自关联
export function updateActualWarehouse(data) {
return request({

View File

@@ -793,6 +793,52 @@ body {
}
}
// Treeselect 样式
.vue-treeselect {
&__control {
min-height: 24px;
border-radius: 0;
border-color: $--border-color-light;
background: linear-gradient(180deg, #fdfdff 0%, #f4f6fa 100%);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.04);
}
&__single-value,
&__placeholder,
&__input-container {
line-height: 24px;
font-size: 12px;
color: $--color-text-regular;
}
&__control-arrow-container {
height: 24px;
}
&__menu {
background: $--metal-gradient-light;
border: 1px solid $--border-color-light;
border-radius: 6px;
box-shadow: $--metal-shadow;
padding: 4px 0;
}
&__option {
color: $--color-text-regular;
padding: 6px 16px;
font-size: 13px;
&--highlight {
background-color: rgba($--color-primary, .12);
}
&--selected {
background-color: rgba($--color-primary, .20);
color: $--color-text-primary;
}
}
}
// 开关
.el-switch {
.el-switch__core {

View File

@@ -1,156 +1,189 @@
<template>
<div style="width: 100%; max-width: 300px;">
<treeselect
:max-height="200"
<div :class="['actual-warehouse-select', { 'is-block': block }]" :style="wrapperStyle">
<el-select
v-model="innerValue"
:options="warehouseOptions"
:normalizer="normalizer"
:placeholder="placeholder"
:clearable="clearable"
search-nested
:load-options="loadOptions"
@input="onInput"
/>
filterable
:filter-method="handleFilter"
:disabled="disabled"
size="small"
class="actual-warehouse-select__inner"
@change="handleChange"
@visible-change="handleVisibleChange"
>
<el-option
v-for="option in displayOptions"
:key="option.actualWarehouseId"
:label="option.fullLabel"
:value="option.actualWarehouseId"
>
<div class="option-content">
<span class="level-tag">{{ levelLabels[option.level] }}</span>
<span class="option-text">{{ option.fullLabel }}</span>
</div>
</el-option>
</el-select>
</div>
</template>
<script>
import Treeselect from '@riophae/vue-treeselect';
import '@riophae/vue-treeselect/dist/vue-treeselect.css';
import { listActualWarehouse } from '@/api/wms/actualWarehouse';
import { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect';
import { listActualWarehouseTree } from '@/api/wms/actualWarehouse';
const LEVEL_LABELS = {
1: '一级',
2: '二级',
3: '三级'
};
export default {
name: 'ActualWarehouseSelect',
components: { Treeselect },
props: {
value: {
type: [Number, String, null], // 仅保留单选类型
type: [Number, String, null],
default: null
},
placeholder: {
type: String,
default: '请选择库区/仓库/库位'
default: '请选择实际库区/库位'
},
clearable: {
type: Boolean,
default: true
},
showTop: {
block: {
type: Boolean,
default: true // 是否显示顶级节点actualWarehouseId=0
default: false
},
width: {
type: [String, Number],
default: 240
},
disabled: {
type: Boolean,
default: false
}
},
data() {
return {
innerValue: this.value,
warehouseOptions: [], // 初始选项(含未加载子节点的节点,标记 children: null
loadedChildren: {}, // 缓存已加载的子节点key=父节点IDvalue=子节点数组
allLoadedNodes: [] // 存储所有已加载的节点,用于快速查找完整对象
flatOptions: [],
displayOptions: [],
levelLabels: LEVEL_LABELS
};
},
computed: {
wrapperStyle() {
const widthValue = this.block ? '100%' : (typeof this.width === 'number' ? `${this.width}px` : this.width);
return { width: widthValue };
}
},
watch: {
value(val) {
this.innerValue = val;
},
}
},
mounted() {
this.initOptions();
created() {
this.fetchTree();
},
methods: {
/** 初始化顶级选项 */
initOptions() {
// 重置状态
this.loadedChildren = {};
this.allLoadedNodes = [];
this.warehouseOptions = [];
if (this.showTop) {
// 显示顶级节点:标记 children: null 表示需要懒加载子节点
const topNode = {
actualWarehouseId: 0,
actualWarehouseName: '顶级节点',
children: null, // 关键:标记为未加载子节点
isDisabled: false
};
this.warehouseOptions.push(topNode);
this.allLoadedNodes.push(topNode);
} else {
// 不显示顶级节点:直接加载 parentId=0 的节点作为顶级(初始标记为未加载)
this.warehouseOptions.push({
actualWarehouseId: 'temp-parent-0', // 临时父节点ID仅用于触发首次加载
actualWarehouseName: '加载中...',
children: null,
isDisabled: true
});
// 触发首次加载 parentId=0 的节点
this.loadOptions({
action: LOAD_CHILDREN_OPTIONS,
parentNode: { id: 0 },
callback: (children) => {
this.warehouseOptions = children;
}
});
async fetchTree() {
try {
const res = await listActualWarehouseTree();
this.flatOptions = this.flattenTree(res.data || []);
this.displayOptions = [...this.flatOptions];
} catch (error) {
console.error('获取实际库区树失败', error);
}
},
/** 懒加载核心方法 */
loadOptions({ action, parentNode, callback, instanceId }) {
// 仅处理 "加载子节点" 动作
if (action !== LOAD_CHILDREN_OPTIONS) {
callback();
flattenTree(nodes, parentPath = [], level = 1) {
const result = [];
nodes.forEach(node => {
const path = [...parentPath, node.actualWarehouseName];
const current = {
actualWarehouseId: node.actualWarehouseId,
parentId: node.parentId || 0,
level,
fullLabel: path.join(' / '),
descendantIds: [],
children: node.children || []
};
result.push(current);
if (node.children && node.children.length) {
const children = this.flattenTree(node.children, path, level + 1);
children.forEach(child => {
current.descendantIds.push(child.actualWarehouseId);
current.descendantIds.push(...child.descendantIds);
});
result.push(...children);
}
});
return result;
},
handleFilter(keyword) {
const text = (keyword || '').trim();
if (!text) {
this.displayOptions = [...this.flatOptions];
return;
}
console.log('加载子节点请求参数:', parentNode);
const parentId = parentNode.actualWarehouseId; // 当前父节点ID对应 actualWarehouseId
// 2. 调用接口加载子节点parentId 作为查询条件)
listActualWarehouse({ parentId }).then(response => {
const children = response.data.map(item => ({
...item,
isDisabled: !item.isEnabled, // 禁用未启用的节点
children: item.hasChildren ?? true ? null : [] // 有子节点则标记 children: null需懒加载否则空数组
}));
// 如果没有子节点了则不添加children属性
if (children.length === 0) {
delete parentNode.children;
callback()
return;
const matchedIds = new Set();
this.flatOptions.forEach(option => {
if (option.fullLabel.includes(text)) {
matchedIds.add(option.actualWarehouseId);
option.descendantIds.forEach(id => matchedIds.add(id));
}
// 5. 给父节点赋值子节点treeselect 自动渲染)
parentNode.children = children;
// 6. 回调通知加载成功
callback();
}).catch((error) => {
console.error('加载子节点失败:', error);
callback(new Error('加载失败,请重试'));
});
this.displayOptions = this.flatOptions.filter(option => matchedIds.has(option.actualWarehouseId));
},
/** 节点格式标准化 */
normalizer(node) {
return {
id: node.actualWarehouseId, // 节点唯一ID
label: node.actualWarehouseName, // 显示文本
children: node.children, // 子节点null=未加载,[]=无节点)
isDisabled: node.isDisabled, // 是否禁用
raw: node // 保留原始节点数据,方便后续查找
};
handleVisibleChange(visible) {
if (!visible) {
this.displayOptions = [...this.flatOptions];
}
},
/** 选中值变化时触发(仅单选) */
onInput(val) {
handleChange(val) {
this.$emit('input', val);
const node = this.flatOptions.find(option => option.actualWarehouseId === val);
this.$emit('select', node);
},
/** 外部刷新方法(如需重新加载数据可调用) */
refresh() {
this.initOptions();
this.fetchTree();
}
}
};
</script>
</script>
<style scoped>
.actual-warehouse-select {
display: inline-block;
}
.actual-warehouse-select.is-block {
width: 100%;
}
.actual-warehouse-select__inner {
width: 100%;
}
.option-content {
display: flex;
align-items: center;
gap: 8px;
}
.level-tag {
font-size: 12px;
color: #909399;
width: 34px;
}
.option-text {
font-size: 13px;
color: #606266;
}
</style>

View File

@@ -114,9 +114,9 @@
</template>
</el-table-column>
<el-table-column label="扫码时间" align="center" prop="scanTime" width="155" :show-overflow-tooltip="true">
<el-table-column label="新增时间" align="center" prop="createTime" width="155" :show-overflow-tooltip="true">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.scanTime, '{y}-{m}-{d} {h}:{i}') }}</span>
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>

View File

@@ -11,7 +11,11 @@
<!-- 真实库区 -->
<el-form-item label="真实库区">
<ActualWarehouseSelect v-model="queryParams.actualWarehouseId" @change="handleActualWarehouseChange" />
<ActualWarehouseSelect
v-model="queryParams.actualWarehouseId"
@change="handleActualWarehouseChange"
:width="220"
/>
</el-form-item>
</el-form>

View File

@@ -1,25 +1,212 @@
<template>
<BasePage :qrcode="qrcode" :querys="querys" :labelType="labelType" :hideType="hideType" />
<div
:class="['actual-warehouse-select', { 'is-block': block }]"
:style="wrapperStyle"
>
<el-cascader
ref="cascader"
v-model="innerPath"
:props="cascaderProps"
:placeholder="placeholder"
:clearable="clearable"
:show-all-levels="true"
:emit-path="true"
style="width: 100%;"
@change="handleChange"
/>
</div>
</template>
<script>
import BasePage from './panels/base.vue';
import { listActualWarehouse } from '@/api/wms/actualWarehouse';
export default {
components: {
BasePage
name: 'ActualWarehouseSelect',
props: {
// 对外仍然是「单个 ID」即第三级 actualWarehouseId
value: {
type: [Number, String, null],
default: null
},
placeholder: {
type: String,
default: '请选择实际库位'
},
clearable: {
type: Boolean,
default: true
},
// 已经不再显示“最高级”
showTop: {
type: Boolean,
default: false
},
block: {
type: Boolean,
default: false
},
width: {
type: [String, Number],
default: 240
},
size: {
type: String,
default: 'small'
}
},
data() {
return {
qrcode: true,
querys: {
dataType: 1,
// itemType: 'raw_material'
materialType: '原料'
},
labelType: '2',
hideType: true
// 级联组件内部使用的「路径值」,例如 [一级ID, 二级ID, 三级ID]
innerPath: [],
// 记录所有已加载节点(如果后面要根据 ID 反查路径,可复用)
allLoadedNodes: []
};
},
computed: {
wrapperStyle() {
const widthValue = this.block
? '100%'
: (typeof this.width === 'number' ? `${this.width}px` : this.width);
return { width: widthValue };
},
// el-cascader 的 props 配置
cascaderProps() {
return {
lazy: true,
lazyLoad: this.loadNode, // 懒加载方法
checkStrictly: false, // 只允许选叶子节点
value: 'value',
label: 'label',
children: 'children'
};
}
},
watch: {
// 外部把 value 置空时,同步清空面板
value(val) {
if (val == null || val === '') {
this.innerPath = [];
}
}
},
methods: {
/**
* 级联懒加载
* node.level:
* 0还没选任何东西parentId = 0 -> 加载一级)
* 1一级节点加载二级
* 2二级节点加载三级三级设为叶子不再展开
*/
loadNode(node, resolve) {
const { level, value } = node;
// 超过第三级就不再加载
if (level >= 3) {
resolve([]);
return;
}
const parentId = level === 0 ? 0 : value;
listActualWarehouse({ parentId }).then(res => {
const list = (res.data || []).map(item => {
const nextLevel = level + 1;
const isLeafLevel = nextLevel >= 3; // 第三级为叶子,不能再展开
const nodeData = {
value: item.actualWarehouseId,
label: item.actualWarehouseName,
leaf: isLeafLevel,
// 只有第三级可选;一、二级全部 disabled
disabled: nextLevel < 3 || item.isEnabled === false,
// 保留原始数据和层级
raw: item,
level: nextLevel
};
this.registerNode(nodeData);
return nodeData;
});
resolve(list);
}).catch(err => {
console.error('加载仓库树失败:', err);
resolve([]);
});
},
/** 把节点放进缓存数组里,后面如需扩展可用 */
registerNode(node) {
const id = node.value;
const idx = this.allLoadedNodes.findIndex(
n => String(n.value) === String(id)
);
if (idx === -1) {
this.allLoadedNodes.push(node);
} else {
this.$set(this.allLoadedNodes, idx, {
...this.allLoadedNodes[idx],
...node
});
}
},
/**
* 选中变化:
* value 是路径数组,如 [一级ID, 二级ID, 三级ID]
* 我们对外只抛最后一个第三级ID
*/
handleChange(value) {
if (!Array.isArray(value) || value.length === 0) {
this.$emit('input', null);
this.$emit('select', null);
return;
}
const leafId = value[value.length - 1];
// 拿到当前选中节点,获取完整路径信息
const nodes = this.$refs.cascader.getCheckedNodes();
let payload = null;
if (nodes && nodes.length > 0) {
const node = nodes[0];
const pathNodes = node.path || [];
payload = {
id: leafId,
pathIds: pathNodes.map(n => n.value),
pathLabels: pathNodes.map(n => n.label),
// 原始 data如果需要后台做别的处理可以用
rawPath: pathNodes.map(n => n.data || n.raw || {})
};
}
this.$emit('input', leafId);
this.$emit('select', payload);
},
/** 外部刷新:如果以后需要强制重载,可以扩展这里(目前不需要缓存) */
refresh() {
this.innerPath = [];
this.allLoadedNodes = [];
// el-cascader 的懒加载不需要预载,这里清空即可
}
}
};
</script>
<style scoped>
.actual-warehouse-select {
display: inline-block;
width: 100%;
}
</script>
.actual-warehouse-select.is-block {
width: 100%;
}
/* 让级联宽度占满容器,其他高度、边框等跟 el-select 共用全局 .el-input__inner 样式 */
.actual-warehouse-select .el-cascader {
width: 100%;
}
</style>

View File

@@ -15,13 +15,13 @@
<el-option :value="1" label="当前数据">当前数据</el-option>
</el-select>
</el-form-item> -->
<el-form-item label="所在库位" prop="warehouseId" v-if="!hideWarehouseQuery">
<el-form-item label="逻辑库位" prop="warehouseId" v-if="!hideWarehouseQuery">
<warehouse-select v-model="queryParams.warehouseId" placeholder="请选择仓库/库区/库位"
style="width: 100%; display: inline-block;" clearable />
</el-form-item>
<el-form-item label="实际库区" prop="actualWarehouseId" v-if="!hideWarehouseQuery">
<actual-warehouse-select v-model="queryParams.actualWarehouseId" placeholder="请选择实际库位"
style="width: 100%; display: inline-block;" clearable />
style="display: inline-block;" clearable />
</el-form-item>
<el-form-item label="厂家卷号" prop="supplierCoilNo">
<el-input v-model="queryParams.supplierCoilNo" placeholder="请输入厂家原料卷号" clearable
@@ -69,7 +69,7 @@
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="materialCoilList" @selection-change="handleSelectionChange" height="450px">
<el-table v-loading="loading" :data="materialCoilList" @selection-change="handleSelectionChange" >
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="入场钢卷号" align="center" prop="enterCoilNo" />
<el-table-column label="当前钢卷号" align="center" prop="currentCoilNo" />

View File

@@ -19,7 +19,7 @@ export default {
},
labelType: '3',
showStatus: true,
hideType: true,
hideType: false,
}
}
}

View File

@@ -171,8 +171,12 @@
</el-select>
</el-form-item>
<el-form-item label="真实库区" required>
<ActualWarehouseSelect v-model="item.actualWarehouseId" placeholder="请选择真实库区" style="width: 100%"
filterable :disabled="readonly" />
<ActualWarehouseSelect
v-model="item.actualWarehouseId"
placeholder="请选择真实库区"
block
:disabled="readonly"
/>
</el-form-item>
</el-form>
</div>

View File

@@ -167,8 +167,12 @@
</el-form-item>
<el-form-item label="真实库区" prop="warehouseId">
<ActualWarehouseSelect v-model="updateForm.actualWarehouseId" placeholder="请选择真实库区" style="width: 100%"
filterable :disabled="readonly" />
<ActualWarehouseSelect
v-model="updateForm.actualWarehouseId"
placeholder="请选择真实库区"
block
:disabled="readonly"
/>
</el-form-item>
<el-form-item label="备注" prop="remark">

File diff suppressed because it is too large Load Diff

View File

@@ -2,10 +2,13 @@ package com.klp.controller;
import java.util.List;
import java.util.Arrays;
import java.util.ArrayList;
import lombok.RequiredArgsConstructor;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.*;
import org.springframework.http.MediaType;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import com.klp.common.annotation.RepeatSubmit;
@@ -16,9 +19,13 @@ import com.klp.common.core.domain.R;
import com.klp.common.core.validate.AddGroup;
import com.klp.common.core.validate.EditGroup;
import com.klp.common.enums.BusinessType;
import com.klp.common.excel.ExcelResult;
import com.klp.common.utils.poi.ExcelUtil;
import com.klp.domain.vo.WmsActualWarehouseVo;
import com.klp.domain.vo.WmsActualWarehouseTreeVo;
import com.klp.domain.vo.WmsActualWarehouseImportVo;
import com.klp.domain.bo.WmsActualWarehouseBo;
import com.klp.domain.bo.WmsActualWarehouseHierarchyBo;
import com.klp.service.IWmsActualWarehouseService;
/**
@@ -44,6 +51,34 @@ public class WmsActualWarehouseController extends BaseController {
return R.ok(list);
}
/**
* 获取完整三级目录树
*/
@GetMapping("/tree")
public R<List<WmsActualWarehouseTreeVo>> tree(WmsActualWarehouseBo bo) {
return R.ok(iWmsActualWarehouseService.queryTree(bo));
}
/**
* 导入实际库区/库位数据
*/
@Log(title = "实际库区/库位自关联", businessType = BusinessType.IMPORT)
@PostMapping(value = "/importData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R<Void> importData(@RequestPart("file") MultipartFile file) throws Exception {
ExcelResult<WmsActualWarehouseImportVo> result = ExcelUtil.importExcel(file.getInputStream(), WmsActualWarehouseImportVo.class, true);
iWmsActualWarehouseService.importHierarchy(result.getList());
return R.ok(result.getAnalysis());
}
/**
* 下载导入模板
*/
@PostMapping("/importTemplate")
public void importTemplate(HttpServletResponse response) {
List<WmsActualWarehouseImportVo> template = buildTemplateData();
ExcelUtil.exportExcel(template, "实际库区导入模板", WmsActualWarehouseImportVo.class, response);
}
/**
* 导出实际库区/库位自关联列表
*/
@@ -75,6 +110,15 @@ public class WmsActualWarehouseController extends BaseController {
return toAjax(iWmsActualWarehouseService.insertByBo(bo));
}
/**
* 批量新增或复用层级目录
*/
@Log(title = "实际库区/库位自关联-批量新增", businessType = BusinessType.INSERT)
@PostMapping("/hierarchy")
public R<List<Long>> addHierarchy(@Validated @RequestBody WmsActualWarehouseHierarchyBo bo) {
return R.ok(iWmsActualWarehouseService.createHierarchy(bo));
}
/**
* 修改实际库区/库位自关联
*/
@@ -96,4 +140,30 @@ public class WmsActualWarehouseController extends BaseController {
@PathVariable Long[] actualWarehouseIds) {
return toAjax(iWmsActualWarehouseService.deleteWithValidByIds(Arrays.asList(actualWarehouseIds), true));
}
private List<WmsActualWarehouseImportVo> buildTemplateData() {
List<WmsActualWarehouseImportVo> list = new ArrayList<>();
list.add(buildRow("L1-001", "一号库区", 10L, "L1-001-A", "A通道", 10L, "L1-001-A-01", "A-01货位", 10L));
list.add(buildRow("L1-001", "一号库区", 10L, "L1-001-A", "A通道", 10L, "L1-001-A-02", "A-02货位", 20L));
list.add(buildRow("L1-001", "一号库区", 10L, "L1-001-B", "B通道", 20L, "L1-001-B-01", "B-01货位", 10L));
list.add(buildRow("L1-002", "二号库区", 20L, "L1-002-A", "冷藏区", 10L, null, null, null));
list.add(buildRow("L1-002", "二号库区", 20L, "L1-002-B", "常温区", 20L, "L1-002-B-01", "常温-01", 10L));
return list;
}
private WmsActualWarehouseImportVo buildRow(String l1Code, String l1Name, Long l1Sort,
String l2Code, String l2Name, Long l2Sort,
String l3Code, String l3Name, Long l3Sort) {
WmsActualWarehouseImportVo vo = new WmsActualWarehouseImportVo();
vo.setLevelOneCode(l1Code);
vo.setLevelOneName(l1Name);
vo.setLevelOneSort(l1Sort);
vo.setLevelTwoCode(l2Code);
vo.setLevelTwoName(l2Name);
vo.setLevelTwoSort(l2Sort);
vo.setLevelThreeCode(l3Code);
vo.setLevelThreeName(l3Name);
vo.setLevelThreeSort(l3Sort);
return vo;
}
}

View File

@@ -0,0 +1,64 @@
package com.klp.domain.bo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.Valid;
import javax.validation.constraints.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* 用于批量新增/匹配一级-三级节点的请求体
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class WmsActualWarehouseHierarchyBo implements Serializable {
@NotEmpty(message = "三级目录数据不能为空")
@Valid
private List<HierarchyLevel> levels = new ArrayList<>();
@Data
public static class HierarchyLevel implements Serializable {
/**
* 层级1/2/3
*/
@NotNull(message = "层级不能为空")
@Min(value = 1, message = "层级最小为1")
@Max(value = 3, message = "层级最大为3")
private Integer level;
/**
* 选中了已有节点时携带的ID用于比对去重
*/
private Long actualWarehouseId;
/**
* 编码(当 actualWarehouseId 为空时必填)
*/
private String actualWarehouseCode;
/**
* 名称(当 actualWarehouseId 为空时必填)
*/
private String actualWarehouseName;
/**
* 同级排序号
*/
private Long sortNo;
/**
* 状态
*/
private Integer isEnabled;
/**
* 备注
*/
private String remark;
}
}

View File

@@ -0,0 +1,41 @@
package com.klp.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
/**
* 实际库区/库位导入模板
*/
@Data
@ExcelIgnoreUnannotated
public class WmsActualWarehouseImportVo {
@ExcelProperty("一级编码")
private String levelOneCode;
@ExcelProperty("一级名称")
private String levelOneName;
@ExcelProperty("一级排序号")
private Long levelOneSort;
@ExcelProperty("二级编码")
private String levelTwoCode;
@ExcelProperty("二级名称")
private String levelTwoName;
@ExcelProperty("二级排序号")
private Long levelTwoSort;
@ExcelProperty("三级编码")
private String levelThreeCode;
@ExcelProperty("三级名称")
private String levelThreeName;
@ExcelProperty("三级排序号")
private Long levelThreeSort;
}

View File

@@ -0,0 +1,18 @@
package com.klp.domain.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.ArrayList;
import java.util.List;
/**
* 树形结构视图对象,用于一次性返回三级目录
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class WmsActualWarehouseTreeVo extends WmsActualWarehouseVo {
private List<WmsActualWarehouseTreeVo> children = new ArrayList<>();
}

View File

@@ -1,8 +1,10 @@
package com.klp.service;
import com.klp.domain.WmsActualWarehouse;
import com.klp.domain.vo.WmsActualWarehouseVo;
import com.klp.domain.vo.WmsActualWarehouseTreeVo;
import com.klp.domain.vo.WmsActualWarehouseImportVo;
import com.klp.domain.bo.WmsActualWarehouseBo;
import com.klp.domain.bo.WmsActualWarehouseHierarchyBo;
import java.util.Collection;
import java.util.List;
@@ -26,11 +28,26 @@ public interface IWmsActualWarehouseService {
*/
List<WmsActualWarehouseVo> queryList(WmsActualWarehouseBo bo);
/**
* 查询完整的三级目录树
*/
List<WmsActualWarehouseTreeVo> queryTree(WmsActualWarehouseBo bo);
/**
* 新增实际库区/库位自关联
*/
Boolean insertByBo(WmsActualWarehouseBo bo);
/**
* 通过一次提交批量创建/复用三级目录
*/
List<Long> createHierarchy(WmsActualWarehouseHierarchyBo bo);
/**
* 导入层级目录
*/
void importHierarchy(List<WmsActualWarehouseImportVo> importList);
/**
* 修改实际库区/库位自关联
*/

View File

@@ -1,20 +1,25 @@
package com.klp.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.klp.common.exception.ServiceException;
import com.klp.common.utils.StringUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.klp.domain.bo.WmsActualWarehouseBo;
import com.klp.domain.bo.WmsActualWarehouseHierarchyBo;
import com.klp.domain.vo.WmsActualWarehouseTreeVo;
import com.klp.domain.vo.WmsActualWarehouseImportVo;
import com.klp.domain.vo.WmsActualWarehouseVo;
import com.klp.domain.WmsActualWarehouse;
import com.klp.mapper.WmsActualWarehouseMapper;
import com.klp.service.IWmsActualWarehouseService;
import java.util.List;
import java.util.Map;
import java.util.Collection;
import java.util.*;
import java.util.stream.Collectors;
/**
* 实际库区/库位自关联Service业务层处理
@@ -46,6 +51,36 @@ public class WmsActualWarehouseServiceImpl implements IWmsActualWarehouseService
return baseMapper.selectVoList(lqw);
}
@Override
public List<WmsActualWarehouseTreeVo> queryTree(WmsActualWarehouseBo bo) {
if (bo == null) {
bo = new WmsActualWarehouseBo();
}
LambdaQueryWrapper<WmsActualWarehouse> wrapper = Wrappers.lambdaQuery();
wrapper.eq(StringUtils.isNotBlank(bo.getActualWarehouseCode()), WmsActualWarehouse::getActualWarehouseCode, bo.getActualWarehouseCode());
wrapper.like(StringUtils.isNotBlank(bo.getActualWarehouseName()), WmsActualWarehouse::getActualWarehouseName, bo.getActualWarehouseName());
wrapper.eq(bo.getActualWarehouseType() != null, WmsActualWarehouse::getActualWarehouseType, bo.getActualWarehouseType());
wrapper.eq(bo.getIsEnabled() != null, WmsActualWarehouse::getIsEnabled, bo.getIsEnabled());
wrapper.orderByAsc(WmsActualWarehouse::getSortNo, WmsActualWarehouse::getActualWarehouseId);
List<WmsActualWarehouseVo> flatList = baseMapper.selectVoList(wrapper);
Map<Long, WmsActualWarehouseTreeVo> nodeMap = new LinkedHashMap<>();
flatList.forEach(item -> {
WmsActualWarehouseTreeVo node = new WmsActualWarehouseTreeVo();
BeanUtil.copyProperties(item, node);
nodeMap.put(node.getActualWarehouseId(), node);
});
List<WmsActualWarehouseTreeVo> roots = new ArrayList<>();
nodeMap.values().forEach(node -> {
Long parentId = Optional.ofNullable(node.getParentId()).orElse(0L);
if (parentId == 0 || !nodeMap.containsKey(parentId)) {
roots.add(node);
} else {
nodeMap.get(parentId).getChildren().add(node);
}
});
return roots;
}
private LambdaQueryWrapper<WmsActualWarehouse> buildQueryWrapper(WmsActualWarehouseBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<WmsActualWarehouse> lqw = Wrappers.lambdaQuery();
@@ -82,6 +117,80 @@ public class WmsActualWarehouseServiceImpl implements IWmsActualWarehouseService
return baseMapper.updateById(update) > 0;
}
@Override
@Transactional(rollbackFor = Exception.class)
public List<Long> createHierarchy(WmsActualWarehouseHierarchyBo bo) {
if (bo.getLevels() == null || bo.getLevels().isEmpty()) {
throw new ServiceException("层级数据不能为空");
}
List<WmsActualWarehouseHierarchyBo.HierarchyLevel> sortedLevels = bo.getLevels().stream()
.sorted(Comparator.comparing(WmsActualWarehouseHierarchyBo.HierarchyLevel::getLevel))
.collect(Collectors.toList());
Long parentId = 0L;
List<Long> createdIds = new ArrayList<>();
for (WmsActualWarehouseHierarchyBo.HierarchyLevel level : sortedLevels) {
Integer levelValue = level.getLevel();
if (levelValue == null || levelValue < 1 || levelValue > 3) {
throw new ServiceException("层级必须在1-3之间");
}
if (level.getActualWarehouseId() != null) {
WmsActualWarehouse existing = baseMapper.selectById(level.getActualWarehouseId());
if (existing == null) {
throw new ServiceException("指定的节点不存在:" + level.getActualWarehouseId());
}
if (!Objects.equals(Optional.ofNullable(existing.getParentId()).orElse(0L), parentId)) {
throw new ServiceException("节点不属于当前父级,无法复用");
}
parentId = existing.getActualWarehouseId();
continue;
}
if (StringUtils.isBlank(level.getActualWarehouseCode()) || StringUtils.isBlank(level.getActualWarehouseName())) {
throw new ServiceException("编码与名称不能为空");
}
WmsActualWarehouse duplicate = baseMapper.selectOne(Wrappers.<WmsActualWarehouse>lambdaQuery()
.eq(WmsActualWarehouse::getParentId, parentId)
.and(wrapper -> wrapper.eq(WmsActualWarehouse::getActualWarehouseCode, level.getActualWarehouseCode())
.or()
.eq(WmsActualWarehouse::getActualWarehouseName, level.getActualWarehouseName())));
if (duplicate != null) {
parentId = duplicate.getActualWarehouseId();
continue;
}
WmsActualWarehouse entity = new WmsActualWarehouse();
entity.setParentId(parentId);
entity.setActualWarehouseType(levelValue.longValue());
entity.setActualWarehouseCode(level.getActualWarehouseCode());
entity.setActualWarehouseName(level.getActualWarehouseName());
entity.setSortNo(Optional.ofNullable(level.getSortNo()).orElse(0L));
entity.setIsEnabled(Optional.ofNullable(level.getIsEnabled()).orElse(1));
entity.setRemark(level.getRemark());
baseMapper.insert(entity);
parentId = entity.getActualWarehouseId();
createdIds.add(parentId);
}
return createdIds;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void importHierarchy(List<WmsActualWarehouseImportVo> importList) {
if (CollUtil.isEmpty(importList)) {
throw new ServiceException("导入数据不能为空");
}
for (WmsActualWarehouseImportVo row : importList) {
List<WmsActualWarehouseHierarchyBo.HierarchyLevel> levels = new ArrayList<>();
appendLevel(levels, 1, row.getLevelOneCode(), row.getLevelOneName(), row.getLevelOneSort());
appendLevel(levels, 2, row.getLevelTwoCode(), row.getLevelTwoName(), row.getLevelTwoSort());
appendLevel(levels, 3, row.getLevelThreeCode(), row.getLevelThreeName(), row.getLevelThreeSort());
if (levels.isEmpty()) {
continue;
}
WmsActualWarehouseHierarchyBo bo = new WmsActualWarehouseHierarchyBo();
bo.setLevels(levels);
createHierarchy(bo);
}
}
/**
* 保存前的数据校验
*/
@@ -89,6 +198,23 @@ public class WmsActualWarehouseServiceImpl implements IWmsActualWarehouseService
//TODO 做一些数据校验,如唯一约束
}
private void appendLevel(List<WmsActualWarehouseHierarchyBo.HierarchyLevel> levels,
int level,
String code,
String name,
Long sortNo) {
if (StringUtils.isBlank(code) || StringUtils.isBlank(name)) {
return;
}
WmsActualWarehouseHierarchyBo.HierarchyLevel item = new WmsActualWarehouseHierarchyBo.HierarchyLevel();
item.setLevel(level);
item.setActualWarehouseCode(code.trim());
item.setActualWarehouseName(name.trim());
item.setSortNo(Optional.ofNullable(sortNo).orElse(0L));
item.setIsEnabled(1);
levels.add(item);
}
/**
* 批量删除实际库区/库位自关联
*/

View File

@@ -56,7 +56,7 @@ public class WmsCoilPendingActionServiceImpl implements IWmsCoilPendingActionSer
qw.eq(bo.getCoilId() != null, "wcpa.coil_id", bo.getCoilId());
qw.like(StringUtils.isNotBlank(bo.getCurrentCoilNo()), "wcpa.current_coil_no", bo.getCurrentCoilNo());
qw.eq(bo.getActionType() != null, "wcpa.action_type", bo.getActionType());
qw.eq(bo.getActionStatus() != null, "wcpa.action_status", bo.getActionStatus());
qw.ne(bo.getActionStatus() != null, "wcpa.action_status",2);
qw.eq(bo.getWarehouseId() != null, "wcpa.warehouse_id", bo.getWarehouseId());
qw.eq(bo.getPriority() != null, "wcpa.priority", bo.getPriority());
qw.like(StringUtils.isNotBlank(bo.getSourceType()), "wcpa.source_type", bo.getSourceType());