发货模块本次改造三项能力:
salesLedger/index.vue)— 发货弹窗、批量发货入口deliveryLedger/index.vue)— 发货数量展示| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /shippingInfo/add | 创建发货单并发起发货审批(新增本次发货数量参数) |
| POST | /shippingInfo/batchAdd | 批量发货:同一客户多条订单,逐条创建发货单并发起审批 |
| GET | /sales/product/list | 产品明细列表(响应新增已发数量、剩余可发数量) |
请求参数: 在原有字段基础上新增 quantity(本次发货数量)。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| salesLedgerProductId | Long | 是 | 销售台账产品明细ID |
| quantity | BigDecimal | 否 | 本次发货数量,不传时默认取产品明细全量数量 |
| type | String | 否 | 审批备注前缀(如 部分发货/批量发货) |
| approveUserIds | Array | 否 | 审批人ID列表 |
| tempFileIds | Array | 否 | 临时附件ID列表 |
| expressNumber | String | 否 | 快递单号 |
| expressCompany | String | 否 | 快递公司 |
| shippingCarNumber | String | 否 | 发货车牌号 |
| shippingDate | String | 否 | 发货日期 |
校验规则: quantity 必须大于 0 且不超过"剩余可发数量",否则接口返回错误。
响应: { "code": 200, "msg": "操作成功" }
请求体: 数组,元素为发货单数据(字段同 /shippingInfo/add)。
[
{
"salesLedgerId": 1,
"salesLedgerProductId": 100,
"quantity": 10,
"type": "批量发货"
},
{
"salesLedgerId": 2,
"salesLedgerProductId": 200,
"quantity": 5,
"type": "批量发货"
}
]
校验规则: 所有产品明细所属销售台账必须为**同一客户**,否则返回错误"批量发货仅支持同一客户的多条订单"。每条数据独立生成发货编号并独立发起审批。
响应: { "code": 200, "msg": "操作成功" }
响应中每条产品明细新增两个字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| alreadyShippedQuantity | BigDecimal | 已发数量(状态为"审核通过 + 已发货"的发货记录数量之和) |
| unshippedQuantity | BigDecimal | 剩余可发数量 = 产品明细数量 − 已发数量 |
canShip 原逻辑要求 hasStockInventory(产品必须有库存记录)且发货状态为"待发货/审核拒绝",导致未完工/产量不足时发货按钮被锁定。改造后**移除 !row.hasStockInventory 限制**,改为判断"剩余可发数量 > 0"。
// 原逻辑
canShip(row) {
return row.hasStockInventory
&& (row.shippingStatus === '待发货' || row.shippingStatus === '审核拒绝');
}
// 新逻辑:解除完工/库存锁定,仅需存在剩余可发数量
canShip(row) {
return row.unshippedQuantity > 0
&& (row.shippingStatus === '待发货' || row.shippingStatus === '审核拒绝');
}
发货弹窗(deliveryForm)新增"本次发货数量"输入框,默认值为 unshippedQuantity(剩余可发数量),并做上限校验。
<el-form-item label="本次发货数量" prop="quantity">
<el-input-number
v-model="deliveryForm.quantity"
:min="0.01"
:max="Number(deliveryForm.unshippedQuantity)"
:precision="2"
placeholder="请输入本次发货数量"
/>
<span style="margin-left: 8px; color: #909399;">剩余可发:{{ deliveryForm.unshippedQuantity }}</span>
</el-form-item>
// data 中初始化
deliveryForm: {
salesLedgerProductId: null,
salesLedgerId: null,
quantity: null, // 本次发货数量
unshippedQuantity: 0, // 剩余可发数量(打开弹窗时赋值)
type: '部分发货',
approveUserIds: [],
tempFileIds: [],
},
// 打开发货弹窗
openDelivery(row) {
this.deliveryForm = {
salesLedgerProductId: row.id,
salesLedgerId: row.salesLedgerId,
quantity: row.unshippedQuantity, // 默认取剩余可发数量
unshippedQuantity: row.unshippedQuantity,
type: '部分发货',
approveUserIds: [],
tempFileIds: [],
};
this.deliveryOpen = true;
},
// 提交发货
submitDelivery() {
const qty = Number(this.deliveryForm.quantity);
if (!qty || qty <= 0) {
this.$modal.msgError('本次发货数量必须大于 0');
return;
}
if (qty > Number(this.deliveryForm.unshippedQuantity)) {
this.$modal.msgError('本次发货数量不能超过剩余可发数量');
return;
}
addShipping(this.deliveryForm).then(res => {
if (res.code === 200) {
this.$modal.msgSuccess('发货成功,已发起审批');
this.deliveryOpen = false;
this.getList(); // 刷新
}
});
}
主表已有 type="selection" 复选框。新增"批量发货"按钮,勾选**同一客户**的多条订单后点击,弹出批量发货弹窗。
<el-button
type="primary"
icon="el-icon-s-order"
@click="handleBatchShipping"
:disabled="selectedLedgers.length === 0"
>批量发货</el-button>
data() {
return {
selectedLedgers: [], // 勾选的销售台账(订单)
batchShippingOpen: false, // 批量发货弹窗开关
batchShippingRows: [], // 可发货产品行列表
};
},
// 勾选变化
handleSelectionChange(selection) {
this.selectedLedgers = selection;
},
// 批量发货:校验同一客户后拉取可发产品行
async handleBatchShipping() {
if (!this.selectedLedgers.length) {
this.$modal.msgWarning('请先勾选要发货的订单');
return;
}
// 同一客户校验(后端也会校验)
const customerNames = [...new Set(this.selectedLedgers.map(l => l.customerName))];
if (customerNames.length > 1) {
this.$modal.msgError('批量发货仅支持同一客户的多条订单');
return;
}
// 拉取各订单的产品明细,收集剩余可发数量 > 0 的行
this.batchShippingRows = [];
for (const ledger of this.selectedLedgers) {
const res = await productList({ salesLedgerId: ledger.id, type: 1 });
const rows = (res.data || []).filter(r => Number(r.unshippedQuantity) > 0);
rows.forEach(r => {
r.salesLedgerId = ledger.id;
r.shipQuantity = r.unshippedQuantity; // 默认本次发货数量 = 剩余可发
});
this.batchShippingRows = this.batchShippingRows.concat(rows);
}
if (!this.batchShippingRows.length) {
this.$modal.msgWarning('所选订单无剩余可发货产品');
return;
}
this.batchShippingOpen = true;
},
// 批量提交
submitBatchShipping() {
// 逐行校验数量
for (const row of this.batchShippingRows) {
const qty = Number(row.shipQuantity);
if (!qty || qty <= 0) {
this.$modal.msgError('存在本次发货数量为空或为 0 的行');
return;
}
if (qty > Number(row.unshippedQuantity)) {
this.$modal.msgError('存在超过剩余可发数量的行');
return;
}
}
const reqs = this.batchShippingRows.map(r => ({
salesLedgerId: r.salesLedgerId,
salesLedgerProductId: r.id,
quantity: r.shipQuantity,
type: '批量发货',
approveUserIds: [],
}));
batchAdd(reqs).then(res => {
if (res.code === 200) {
this.$modal.msgSuccess('批量发货成功,已发起审批');
this.batchShippingOpen = false;
this.getList();
}
});
}
批量发货弹窗表格:
<el-table :data="batchShippingRows" border>
<el-table-column label="产品大类" prop="productCategory" />
<el-table-column label="规格型号" prop="specificationModel" />
<el-table-column label="订单数量" prop="quantity" />
<el-table-column label="剩余可发" prop="unshippedQuantity" />
<el-table-column label="本次发货数量">
<template slot-scope="scope">
<el-input-number
v-model="scope.row.shipQuantity"
:min="0.01"
:max="Number(scope.row.unshippedQuantity)"
:precision="2"
size="small"
/>
</template>
</el-table-column>
</el-table>
deliveryLedger/index.vue 列表建议展示本次发货数量列(对应后端 shipping_info.quantity),区分于订单数量:
<el-table-column label="本次发货数量" prop="quantity" align="center" />
quantity(等于对应产品明细数量),旧数据剩余可发数量显示不受影响。