编辑 | blame | 历史 | 原始文档

发货模块改造:部分发货 + 批量发货 + 解除完工限制

背景

发货模块本次改造三项能力:

  1. 部分发货:系统不再强制要求订单 100% 完工才可发货,发货弹窗可输入"本次发货数量",审批通过后仅按该数量扣减库存。
  2. 解除发货按钮锁定:不再因产品无库存记录 / 产量不足(未完工)锁定"发货"按钮,只要存在剩余可发数量即可发起发货。
  3. 批量发货:支持同一客户多条订单勾选后批量发货,无需逐条操作。

涉及页面

  • 销售台账列表页(salesLedger/index.vue)— 发货弹窗、批量发货入口
  • 发货信息管理页(deliveryLedger/index.vue)— 发货数量展示

API

方法 路径 说明
POST /shippingInfo/add 创建发货单并发起发货审批(新增本次发货数量参数)
POST /shippingInfo/batchAdd 批量发货:同一客户多条订单,逐条创建发货单并发起审批
GET /sales/product/list 产品明细列表(响应新增已发数量、剩余可发数量)

1. POST /shippingInfo/add(请求参数变更)

请求参数: 在原有字段基础上新增 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": "操作成功" }

2. POST /shippingInfo/batchAdd(新增接口)

请求体: 数组,元素为发货单数据(字段同 /shippingInfo/add)。

[
  {
    "salesLedgerId": 1,
    "salesLedgerProductId": 100,
    "quantity": 10,
    "type": "批量发货"
  },
  {
    "salesLedgerId": 2,
    "salesLedgerProductId": 200,
    "quantity": 5,
    "type": "批量发货"
  }
]

校验规则: 所有产品明细所属销售台账必须为**同一客户**,否则返回错误"批量发货仅支持同一客户的多条订单"。每条数据独立生成发货编号并独立发起审批。

响应: { "code": 200, "msg": "操作成功" }

3. GET /sales/product/list(响应新增字段)

响应中每条产品明细新增两个字段:

字段 类型 说明
alreadyShippedQuantity BigDecimal 已发数量(状态为"审核通过 + 已发货"的发货记录数量之和)
unshippedQuantity BigDecimal 剩余可发数量 = 产品明细数量 − 已发数量

前端修改点

1. 解除发货按钮锁定(canShip)

canShip 原逻辑要求 hasStockInventory(产品必须有库存记录)且发货状态为"待发货/审核拒绝",导致未完工/产量不足时发货按钮被锁定。改造后**移除 !row.hasStockInventory 限制**,改为判断"剩余可发数量 > 0"。

// 原逻辑
canShip(row) {
  return row.hasStockInventory
    && (row.shippingStatus === '待发货' || row.shippingStatus === '审核拒绝');
}

// 新逻辑:解除完工/库存锁定,仅需存在剩余可发数量
canShip(row) {
  return row.unshippedQuantity > 0
    && (row.shippingStatus === '待发货' || row.shippingStatus === '审核拒绝');
}

2. 发货弹窗新增"本次发货数量"

发货弹窗(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(); // 刷新
    }
  });
}

3. 批量发货按钮 + 弹窗

主表已有 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>

4. 发货信息管理页展示

deliveryLedger/index.vue 列表建议展示本次发货数量列(对应后端 shipping_info.quantity),区分于订单数量:

<el-table-column label="本次发货数量" prop="quantity" align="center" />

注意事项

  • 剩余可发数量口径:仅"审核通过 + 已发货"状态发货单占用数量;"待审核 / 审核中 / 审核拒绝"不占数量,防止超发。
  • 批量发货的客户校验:前端与后端均有校验,仅支持同一客户多条订单。
  • 部分发货扣库存:审批通过后按"本次发货数量"扣减库存,而非订单全量。
  • 删除产品明细:会级联删除其发货单,剩余可发数量随之恢复。
  • 历史数据:存量发货单已由数据库迁移回填 quantity(等于对应产品明细数量),旧数据剩余可发数量显示不受影响。