fix:发货管理(指定发货)

dev_1.0.2
jiangpeng 2026-06-26 17:27:08 +08:00
parent 954be37aa4
commit 9ee56a83a7
6 changed files with 231 additions and 7 deletions

View File

@ -89,3 +89,13 @@ export function importSnData(formData) {
data: formData
})
}
export function assignDeliverySnData(formData) {
return request({
url: '/inventory/outer/vue/assignDeliveryData',
method: 'post',
headers: {
'Content-Type': 'multipart/form-data'
},
data: formData
})
}

View File

@ -121,16 +121,16 @@
<dict-tag :options="dict.type.execution_outer_status" :value="scope.row.outerStatus"/>
</template>
</el-table-column>
<el-table-column label="签收状态" align="center" prop="signStatus">
<template slot-scope="scope">
<dict-tag :options="dict.type.execution_sign_status" :value="scope.row.signStatus"/>
</template>
</el-table-column>
<el-table-column label="发货状态" align="center" prop="deliveryStatus">
<template slot-scope="scope">
<dict-tag :options="dict.type.execution_delivery_status" :value="scope.row.deliveryStatus"/>
</template>
</el-table-column>
<el-table-column label="签收状态" align="center" prop="signStatus">
<template slot-scope="scope">
<dict-tag :options="dict.type.execution_sign_status" :value="scope.row.signStatus"/>
</template>
</el-table-column>
<el-table-column label="订单生效时间" align="center" prop="approveTime" sortable="custom" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.approveTime, '{y}-{m}-{d}') }}</span>

View File

@ -73,6 +73,9 @@
<el-col :span="1.5">
<el-button type="info" plain icon="el-icon-upload2" size="mini" @click="handleSelectPurchaseBeforeImport"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="el-icon-upload2" size="mini" @click="handleSelectPurchaseBeforeAssign"></el-button>
</el-col>
<el-col :span="21">
<div style="text-align: right;">已选: {{ selectedSnList.length }} ()</div>
</el-col>
@ -92,6 +95,7 @@
<purchase-order-select-dialog :visible.sync="purchaseOrderSelectVisible" :productCode="queryParams.productCode" :quantity="queryParams.pageSize" :productTypeList="queryProductType" @select="handlePurchaseOrderSelect"/>
<input type="file" ref="uploadInput" @change="onFileChange" accept=".xls,.xlsx" style="display: none" />
<input type="file" ref="assignUploadInput" @change="onAssignFileChange" accept=".xls,.xlsx" style="display: none" />
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
@ -102,7 +106,7 @@
<script>
import { addDelivery, listProductSn } from '@/api/inventory/delivery';
import {importSnData,exportDownloadTemplate} from '@/api/inventory/outer'
import {importSnData,exportDownloadTemplate,assignDeliverySnData} from '@/api/inventory/outer'
import PurchaseOrderSelectDialog from '../../../purchaseorder/components/PurchaseOrderSelectDialog.vue';
import {handleTree} from "@/utils/ruoyi";
import { productMatchBindList } from '@/api/project/order';
@ -153,6 +157,7 @@ export default {
warehouseName: '',
purchaseOrderList: [],
activePurchaseNo: '',
isAssignMode: false,
};
},
computed: {
@ -273,23 +278,46 @@ export default {
},
handleSelectPurchaseBeforeImport() {
// if (((this.productData.orderType || '1') === '1' ) && !this.outerData.vendorName.startsWith('') ) {
this.isAssignMode = false;
this.purchaseOrderSelectVisible = true;
// }else{
// this.handleImport()
// }
},
handleSelectPurchaseBeforeAssign() {
this.isAssignMode = true;
this.purchaseOrderSelectVisible = true;
},
handlePurchaseOrderSelect(order) {
//
if (this.isAssignMode) {
const isInFilteredList = this.filteredPurchaseOrderList.some(
purchase => purchase.purchaseNo === order.purchaseNo
);
if (!isInFilteredList) {
this.$message.error('指定发货只能选择已绑定的采购单');
return;
}
}
this.warehouseId = order.warehouseId;
this.warehouseName = order.warehouseName;
this.price = order.price;
this.taxRate = order.taxRate;
this.form.purchaseNo = order.purchaseNo;
this.form.itemId = order.itemId;
this.handleImport();
if (this.isAssignMode) {
this.handleAssign();
} else {
this.handleImport();
}
},
handleImport() {
this.$refs.uploadInput.click();
},
handleAssign() {
this.$refs.assignUploadInput.click();
},
onFileChange(event) {
const file = event.target.files[0];
if (!file) return;
@ -328,6 +356,49 @@ export default {
});
this.$refs.uploadInput.value = ''; // Reset file input
},
onAssignFileChange(event) {
const file = event.target.files[0];
if (!file) return;
const formData = new FormData();
formData.append('file', file);
formData.append('productCode', this.productData.productCode);
formData.append('orderType', this.productData.orderType);
formData.append('purchaseNo', this.form.purchaseNo);
formData.append('itemId', this.form.itemId);
assignDeliverySnData(formData).then(response => {
this.$message.success('指定发货导入成功');
this.snList = response.data.productSnDataList;
if (this.warehouseName) {
this.snList.forEach(item => {
item.warehouseName = this.warehouseName;
item.warehouseId = this.warehouseId;
item.innerPrice = this.price;
});
}
this.total = this.snList.length;
this.queryParams.pageNum = 1;
this.queryParams.pageSize = this.total;
//
if (this.form.purchaseNo) {
this.activePurchaseNo = this.form.purchaseNo;
this.queryParams.purchaseNo = this.form.purchaseNo;
}
this.$nextTick(() => {
if (this.$refs.snTable) {
this.$refs.snTable.clearSelection();
this.$refs.snTable.toggleAllSelection();
this.selectedSnList = this.snList;
}
});
}).catch(() => {
this.$message.error('指定发货导入失败');
});
this.$refs.assignUploadInput.value = ''; // Reset file input
},
submitForm() {
this.$refs.form.validate(valid => {
if (valid) {
@ -392,6 +463,7 @@ export default {
this.warehouseName = '';
this.purchaseOrderList = [];
this.activePurchaseNo = '';
this.isAssignMode = false;
}
}
};

View File

@ -133,6 +133,34 @@ public class VueInventoryOuterController extends BaseController
}
}
/**
*
*/
@PostMapping("/assignDeliveryData")
@ResponseBody
public AjaxResult assignDeliveryData(@RequestPart("file") MultipartFile file,
@RequestParam(value = "productCode") String productCode,
@RequestParam(value = "orderType", defaultValue = "1") String orderType,
@RequestParam(value = "purchaseNo") String purchaseNo,
@RequestParam(value = "itemId") Long itemId) {
ExcelUtil<InventoryInfoExcelDto> util = new ExcelUtil<InventoryInfoExcelDto>(InventoryInfoExcelDto.class);
try (InputStream inputStream = file.getInputStream()) {
List<InventoryInfoExcelDto> inventoryInfoExcelDtoList = util.importExcel(inputStream);
if (CollUtil.isEmpty(inventoryInfoExcelDtoList)) {
return AjaxResult.error("导入数据为空");
}
return AjaxResult.success(innerService.validateAndGetAssignDeliveryList(
inventoryInfoExcelDtoList, productCode, orderType, purchaseNo, itemId));
} catch (IOException e) {
throw new ServiceException("读取excel错误,请联系管理员");
} catch (ServiceException e) {
return AjaxResult.error(e.getMessage());
}
}
/**
*
*/

View File

@ -66,4 +66,5 @@ public interface IOmsInventoryInnerService
void importByOuter(List<InventoryInfo> inventoryInfoList, String productCode, String purchaseNo,Long itemId);
Map<String,Object> getInventoryInfoList(List<InventoryInfoExcelDto> inventoryInfoExcelDtoList, String productCode, String orderType);
Map<String,Object> validateAndGetAssignDeliveryList(List<InventoryInfoExcelDto> inventoryInfoExcelDtoList, String productCode, String orderType, String purchaseNo, Long itemId);
}

View File

@ -372,5 +372,118 @@ public class OmsInventoryInnerServiceImpl implements IOmsInventoryInnerService {
return result;
}
@Override
public Map<String, Object> validateAndGetAssignDeliveryList(List<InventoryInfoExcelDto> inventoryInfoExcelDtoList,
String productCode, String orderType, String purchaseNo, Long itemId) {
// 1. 判断文件中所有数据的产品编码是否相同
long distinctProductCodeCount = inventoryInfoExcelDtoList.stream()
.map(InventoryInfoExcelDto::getProductCode)
.distinct()
.count();
if (distinctProductCodeCount > 1) {
throw new ServiceException("文件中产品编码不一致,只能包含一个产品编码");
}
String fileProductCode = inventoryInfoExcelDtoList.get(0).getProductCode();
if (!fileProductCode.equals(productCode)) {
throw new ServiceException("文件中的产品编码与当前选择的产品编码不一致");
}
// 2. 判断文件中入库价是否相同
long distinctPriceCount = inventoryInfoExcelDtoList.stream()
.map(InventoryInfoExcelDto::getInnerPrice)
.filter(Objects::nonNull)
.distinct()
.count();
if (distinctPriceCount > 1) {
throw new ServiceException("文件中入库价不一致,所有产品的入库价必须相同");
}
BigDecimal fileInnerPrice = inventoryInfoExcelDtoList.stream()
.map(InventoryInfoExcelDto::getInnerPrice)
.filter(Objects::nonNull)
.findFirst()
.orElseThrow(() -> new ServiceException("文件中未找到入库价"));
// 3. 判断当前选择的采购单中是否存在这个产品编码
List<OmsPurchaseOrderItem> purchaseOrderItems = purchaseOrderService.listByItemId(itemId);
if (CollUtil.isEmpty(purchaseOrderItems)) {
throw new ServiceException("未找到采购单明细信息");
}
OmsPurchaseOrderItem matchedItem = purchaseOrderItems.stream()
.filter(item -> item.getProductCode().equals(productCode))
.findFirst()
.orElseThrow(() -> new ServiceException("采购单中不存在产品编码:" + productCode));
// 4. 判断采购单中对应的产品价格是否和文件中的入库价相同
BigDecimal purchasePrice = matchedItem.getPrice();
if (purchasePrice == null) {
throw new ServiceException("采购单中未设置产品价格");
}
if (fileInnerPrice.compareTo(purchasePrice) != 0) {
throw new ServiceException(String.format("文件中的入库价[%s]与采购单价格[%s]不一致",
fileInnerPrice.toString(), purchasePrice.toString()));
}
// 5. 判断文件中的SN码是否未发货未绑定订单outer_code和order_code都为空
List<String> snList = inventoryInfoExcelDtoList.stream()
.map(InventoryInfoExcelDto::getProductSn)
.collect(Collectors.toList());
List<InventoryInfo> existingInventoryList = inventoryInfoService.listByProductSnList(snList);
if (CollUtil.isNotEmpty(existingInventoryList)) {
// 检查是否有已发货或已绑定订单的SN码
List<String> invalidSnList = existingInventoryList.stream()
.filter(item -> StringUtils.isNotEmpty(item.getOuterCode()) || StringUtils.isNotEmpty(item.getOrderCode()))
.map(InventoryInfo::getProductSn)
.collect(Collectors.toList());
if (CollUtil.isNotEmpty(invalidSnList)) {
// 最多显示10条SN码超过的用省略号表示
String snDisplay;
if (invalidSnList.size() <= 10) {
snDisplay = String.join(", ", invalidSnList);
} else {
snDisplay = String.join(", ", invalidSnList.subList(0, 10)) + "...等" + invalidSnList.size() + "条";
}
throw new ServiceException(String.format("以下SN码已发货或已绑定订单不能用于指定发货%s", snDisplay));
}
} else {
throw new ServiceException("文件中的SN码在库存中不存在");
}
// 验证通过,返回库存信息列表
List<ProductInfo> productInfos = productInfoService.selectProductInfoByCodeList(Collections.singletonList(productCode));
if (CollUtil.isEmpty(productInfos)) {
throw new ServiceException("产品编码对应产品未找到");
}
Map<String, InventoryInfo> existingInventoryMap = existingInventoryList.stream()
.collect(Collectors.toMap(InventoryInfo::getProductSn, Function.identity()));
List<InventoryInfo> inventoryInfoList = inventoryInfoExcelDtoList.stream().map(item -> {
InventoryInfo info = existingInventoryMap.get(item.getProductSn());
if (info == null) {
info = new InventoryInfo();
info.setProductSn(item.getProductSn());
info.setProductCode(productCode);
info.setInventoryStatus(InventoryInfo.InventoryStatusEnum.INNER.getCode());
}
info.setLicenseKey(item.getLicenseKey());
info.setModel(productInfos.get(0).getModel());
info.setProductDesc(productInfos.get(0).getDescription());
info.setInnerPrice(item.getInnerPrice());
return info;
}).collect(Collectors.toList());
Map<String, Object> result = new HashMap<>();
result.put("productSnDataList", inventoryInfoList);
return result;
}
}