feat(bid): add pending delivery order management feature
1. 新增待发订单路由页面与菜单权限 2. 新增发货单生成功能,可从已采纳报价单创建发货单 3. 扩展发货单实体与Mapper,增加物料数量统计字段 4. 实现待发订单列表、详情、编辑、发货确认与删除功能
This commit is contained in:
@@ -25,6 +25,7 @@ public class BizDeliveryOrder extends BaseEntity {
|
|||||||
private String deliveryStatus;
|
private String deliveryStatus;
|
||||||
private List<BizDeliveryOrderItem> items;
|
private List<BizDeliveryOrderItem> items;
|
||||||
private String supplierName;
|
private String supplierName;
|
||||||
|
private Integer itemCount;
|
||||||
|
|
||||||
public Long getDoId() { return doId; }
|
public Long getDoId() { return doId; }
|
||||||
public void setDoId(Long doId) { this.doId = doId; }
|
public void setDoId(Long doId) { this.doId = doId; }
|
||||||
@@ -56,4 +57,6 @@ public class BizDeliveryOrder extends BaseEntity {
|
|||||||
public void setItems(List<BizDeliveryOrderItem> items) { this.items = items; }
|
public void setItems(List<BizDeliveryOrderItem> items) { this.items = items; }
|
||||||
public String getSupplierName() { return supplierName; }
|
public String getSupplierName() { return supplierName; }
|
||||||
public void setSupplierName(String supplierName) { this.supplierName = supplierName; }
|
public void setSupplierName(String supplierName) { this.supplierName = supplierName; }
|
||||||
|
public Integer getItemCount() { return itemCount; }
|
||||||
|
public void setItemCount(Integer itemCount) { this.itemCount = itemCount; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,10 +22,12 @@
|
|||||||
<result property="updateBy" column="update_by"/>
|
<result property="updateBy" column="update_by"/>
|
||||||
<result property="updateTime" column="update_time"/>
|
<result property="updateTime" column="update_time"/>
|
||||||
<result property="supplierName" column="supplier_name"/>
|
<result property="supplierName" column="supplier_name"/>
|
||||||
|
<result property="itemCount" column="item_count"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<select id="selectBizDeliveryOrderList" resultMap="BaseRM">
|
<select id="selectBizDeliveryOrderList" resultMap="BaseRM">
|
||||||
SELECT d.*, s.supplier_name
|
SELECT d.*, s.supplier_name,
|
||||||
|
(SELECT COUNT(*) FROM biz_delivery_order_item WHERE do_id = d.do_id) AS item_count
|
||||||
FROM biz_delivery_order d
|
FROM biz_delivery_order d
|
||||||
LEFT JOIN biz_supplier s ON d.supplier_id=s.supplier_id
|
LEFT JOIN biz_supplier s ON d.supplier_id=s.supplier_id
|
||||||
<where>
|
<where>
|
||||||
|
|||||||
@@ -189,6 +189,19 @@ export const dynamicRoutes = [
|
|||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ── 订单履约:待发订单 ──
|
||||||
|
{
|
||||||
|
path: '/bid/order/pending',
|
||||||
|
component: Layout,
|
||||||
|
permissions: ['bid:order:pending'],
|
||||||
|
children: [{
|
||||||
|
path: '',
|
||||||
|
component: () => import('@/views/bid/order/pending'),
|
||||||
|
name: 'OrderPending',
|
||||||
|
meta: { title: '待发订单', activeMenu: '/bid/order' }
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: '/bid/comparison/detail',
|
path: '/bid/comparison/detail',
|
||||||
component: Layout,
|
component: Layout,
|
||||||
|
|||||||
231
ruoyi-ui/src/views/bid/order/pending.vue
Normal file
231
ruoyi-ui/src/views/bid/order/pending.vue
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
<template>
|
||||||
|
<div class="order-page">
|
||||||
|
<!-- ═══ 标题栏 ═══ -->
|
||||||
|
<div class="page-header">
|
||||||
|
<span class="page-title">待发订单</span>
|
||||||
|
<el-tag type="warning" size="small" effect="dark" class="status-tag">STATUS: PENDING</el-tag>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ═══ 搜索栏 ═══ -->
|
||||||
|
<div class="search-bar">
|
||||||
|
<el-input v-model="queryParams.doNo" placeholder="搜索发货单号" clearable size="small" style="width:150px" @keyup.enter.native="handleSearch" />
|
||||||
|
<el-input v-model="queryParams.supplierName" placeholder="搜索供应商名称" clearable size="small" style="width:160px" @keyup.enter.native="handleSearch" />
|
||||||
|
<el-button type="primary" size="small" icon="el-icon-search" @click="handleSearch">搜索</el-button>
|
||||||
|
<el-button size="small" icon="el-icon-refresh" @click="resetSearch">重置</el-button>
|
||||||
|
<div class="search-right">
|
||||||
|
<el-button size="small" icon="el-icon-refresh" @click="getList">刷新</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ═══ 表格 ═══ -->
|
||||||
|
<el-table v-loading="loading" :data="list" border stripe size="small" class="order-table">
|
||||||
|
<el-table-column label="发货单号" prop="doNo" width="170" />
|
||||||
|
<el-table-column label="供应商" prop="supplierName" min-width="150" show-overflow-tooltip />
|
||||||
|
<el-table-column label="金额" width="130" align="right">
|
||||||
|
<template slot-scope="s"><span class="amount">¥{{ s.row.totalAmount }}</span></template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="交货期" prop="deliveryDate" width="100" align="center" />
|
||||||
|
<el-table-column label="延期日期" prop="delayDate" width="100" align="center">
|
||||||
|
<template slot-scope="s">{{ s.row.delayDate || '-' }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="物料" prop="itemCount" width="60" align="center" />
|
||||||
|
<el-table-column label="逾期提示" width="110" align="center">
|
||||||
|
<template slot-scope="s">
|
||||||
|
<span v-html="getUrgentBadge(s.row)" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="状态" width="90" align="center">
|
||||||
|
<template slot-scope="s">
|
||||||
|
<el-tag type="warning" size="small" effect="dark">PENDING</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="210" align="center" fixed="right">
|
||||||
|
<template slot-scope="s">
|
||||||
|
<el-button size="mini" type="text" @click="handleView(s.row)">详情</el-button>
|
||||||
|
<el-button size="mini" type="text" @click="handleEdit(s.row)" v-if="s.row.deliveryStatus==='pending'">编辑</el-button>
|
||||||
|
<el-button size="mini" type="text" style="color:#67C23A" @click="handleShip(s.row)" v-if="s.row.deliveryStatus==='pending'">发货确认</el-button>
|
||||||
|
<el-button size="mini" type="text" style="color:#f56c6c" @click="handleDelete(s.row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
|
||||||
|
|
||||||
|
<!-- ═══ 详情弹窗 ═══ -->
|
||||||
|
<el-dialog title="发货单详情" :visible.sync="detailOpen" width="780px" append-to-body class="detail-dialog">
|
||||||
|
<div v-if="detailData">
|
||||||
|
<div class="detail-grid">
|
||||||
|
<div class="detail-item"><span class="dl">发货单号</span><span class="dv"><b>{{ detailData.doNo }}</b></span></div>
|
||||||
|
<div class="detail-item"><span class="dl">供应商</span><span class="dv">{{ detailData.supplierName || '-' }}</span></div>
|
||||||
|
<div class="detail-item"><span class="dl">总金额</span><span class="dv" style="color:#409EFF;font-weight:700">¥{{ detailData.totalAmount }}</span></div>
|
||||||
|
<div class="detail-item"><span class="dl">状态</span><span class="dv"><el-tag type="warning" size="small" effect="dark">PENDING</el-tag></span></div>
|
||||||
|
<div class="detail-item"><span class="dl">交货期</span><span class="dv">{{ detailData.deliveryDate || '-' }}</span></div>
|
||||||
|
<div class="detail-item"><span class="dl">延期日期</span><span class="dv">{{ detailData.delayDate || '-' }}</span></div>
|
||||||
|
</div>
|
||||||
|
<div v-if="detailData.remark" class="detail-remark">备注:{{ detailData.remark }}</div>
|
||||||
|
<div class="section-bar">物料明细</div>
|
||||||
|
<el-table :data="detailData.items || []" border size="small" style="width:100%">
|
||||||
|
<el-table-column label="物料名称" prop="materialName" min-width="150" />
|
||||||
|
<el-table-column label="规格" prop="spec" width="120" show-overflow-tooltip />
|
||||||
|
<el-table-column label="单位" prop="unit" width="60" />
|
||||||
|
<el-table-column label="数量" prop="quantity" width="80" align="right" />
|
||||||
|
<el-table-column label="单价" width="100" align="right"><template slot-scope="s">¥{{ s.row.unitPrice }}</template></el-table-column>
|
||||||
|
<el-table-column label="小计" width="100" align="right"><template slot-scope="s">¥{{ s.row.totalPrice }}</template></el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
<div slot="footer"><el-button @click="detailOpen = false">关闭</el-button></div>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- ═══ 编辑弹窗 ═══ -->
|
||||||
|
<el-dialog title="编辑发货单" :visible.sync="editOpen" width="600px" append-to-body @close="editOpen = false">
|
||||||
|
<el-form ref="editForm" :model="editForm" label-width="90px" size="small">
|
||||||
|
<el-form-item label="发货单号">{{ editForm.doNo }}</el-form-item>
|
||||||
|
<el-form-item label="供应商">{{ editForm.supplierName }}</el-form-item>
|
||||||
|
<el-form-item label="总金额"><el-input-number v-model="editForm.totalAmount" :min="0" :precision="2" style="width:200px" /></el-form-item>
|
||||||
|
<el-form-item label="交货期"><el-date-picker v-model="editForm.deliveryDate" type="date" value-format="yyyy-MM-dd" style="width:200px" /></el-form-item>
|
||||||
|
<el-form-item label="延期日期"><el-date-picker v-model="editForm.delayDate" type="date" value-format="yyyy-MM-dd" style="width:200px" /></el-form-item>
|
||||||
|
<el-form-item label="备注"><el-input v-model="editForm.remark" type="textarea" :rows="2" /></el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<div slot="footer">
|
||||||
|
<el-button @click="editOpen = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="submitEdit">保存</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { listDelivery, getDelivery, updateDelivery, delDelivery, shipDelivery } from "@/api/bid/delivery"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "OrderPending",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false, list: [], total: 0,
|
||||||
|
queryParams: { pageNum: 1, pageSize: 20, deliveryStatus: "pending", doNo: "", supplierName: "" },
|
||||||
|
detailOpen: false, detailData: null,
|
||||||
|
editOpen: false, editForm: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() { this.getList() },
|
||||||
|
methods: {
|
||||||
|
getList() {
|
||||||
|
this.loading = true
|
||||||
|
listDelivery(this.queryParams).then(r => {
|
||||||
|
this.list = (r.rows || []).map(d => ({
|
||||||
|
...d,
|
||||||
|
deliveryDate: d.deliveryDate ? d.deliveryDate.substring(0, 10) : '',
|
||||||
|
delayDate: d.delayDate ? d.delayDate.substring(0, 10) : '',
|
||||||
|
actualCloseDate: d.actualCloseDate ? d.actualCloseDate.substring(0, 10) : ''
|
||||||
|
}))
|
||||||
|
this.total = r.total || 0
|
||||||
|
this.loading = false
|
||||||
|
}).catch(() => { this.loading = false })
|
||||||
|
},
|
||||||
|
handleSearch() { this.queryParams.pageNum = 1; this.getList() },
|
||||||
|
resetSearch() { this.queryParams.doNo = ""; this.queryParams.supplierName = ""; this.handleSearch() },
|
||||||
|
|
||||||
|
// 详情
|
||||||
|
handleView(row) {
|
||||||
|
getDelivery(row.doId).then(r => {
|
||||||
|
this.detailData = r.data
|
||||||
|
this.detailOpen = true
|
||||||
|
}).catch(() => {})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 编辑
|
||||||
|
handleEdit(row) {
|
||||||
|
this.editForm = { ...row }
|
||||||
|
this.editOpen = true
|
||||||
|
},
|
||||||
|
submitEdit() {
|
||||||
|
updateDelivery(this.editForm).then(() => {
|
||||||
|
this.$modal.msgSuccess("保存成功")
|
||||||
|
this.editOpen = false
|
||||||
|
this.getList()
|
||||||
|
}).catch(() => {})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 发货确认
|
||||||
|
handleShip(row) {
|
||||||
|
this.$modal.confirm("确认标记该发货单为「已发货」?").then(() => {
|
||||||
|
return shipDelivery(row.doId)
|
||||||
|
}).then(() => {
|
||||||
|
this.$modal.msgSuccess("已确认发货")
|
||||||
|
this.getList()
|
||||||
|
}).catch(() => {})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
handleDelete(row) {
|
||||||
|
this.$modal.confirm("确认删除发货单 " + row.doNo + "?").then(() => {
|
||||||
|
return delDelivery(row.doId)
|
||||||
|
}).then(() => {
|
||||||
|
this.$modal.msgSuccess("已删除")
|
||||||
|
this.getList()
|
||||||
|
}).catch(() => {})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 逾期预警
|
||||||
|
getUrgentBadge(row) {
|
||||||
|
if (!row.deliveryDate) return ""
|
||||||
|
const today = new Date(); today.setHours(0, 0, 0, 0)
|
||||||
|
const d = new Date(row.deliveryDate)
|
||||||
|
const diff = Math.round((d - today) / 86400000)
|
||||||
|
if (diff < 0) return '<span class="urgent-overdue">⚠ 逾期' + Math.abs(diff) + '天</span>'
|
||||||
|
if (diff === 0) return '<span class="urgent-overdue">⚠ 今日到期</span>'
|
||||||
|
if (diff <= 3) return '<span class="urgent-soon">⚡ 剩' + diff + '天</span>'
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.order-page { background: #f5f7fa; padding: 12px; min-height: calc(100vh - 84px); }
|
||||||
|
|
||||||
|
/* ═══ 标题栏 ═══ */
|
||||||
|
.page-header {
|
||||||
|
background: #fff; padding: 12px 16px; border-radius: 4px; margin-bottom: 12px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0,0,0,0.06); display: flex; align-items: center; gap: 12px;
|
||||||
|
}
|
||||||
|
.page-title { font-size: 16px; font-weight: 700; color: #1a2c4e; }
|
||||||
|
.status-tag { margin-left: auto; }
|
||||||
|
|
||||||
|
/* ═══ 搜索栏 ═══ */
|
||||||
|
.search-bar {
|
||||||
|
background: #fff; padding: 12px 16px; border-radius: 4px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0,0,0,0.06); margin-bottom: 12px;
|
||||||
|
display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.search-right { margin-left: auto; }
|
||||||
|
|
||||||
|
/* ═══ 表格 ═══ */
|
||||||
|
.order-table { box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
|
||||||
|
.amount { color: #409EFF; font-weight: 700; }
|
||||||
|
.urgent-overdue { color: #f56c6c; font-weight: 700; font-size: 12px; }
|
||||||
|
.urgent-soon { color: #e6a23c; font-weight: 700; font-size: 12px; }
|
||||||
|
|
||||||
|
/* ═══ 详情 ═══ */
|
||||||
|
.detail-grid {
|
||||||
|
display: grid; grid-template-columns: 1fr 1fr; gap: 0;
|
||||||
|
border: 1px solid #ebeef5; border-radius: 4px; margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.detail-item { display: flex; border-bottom: 1px solid #ebeef5; }
|
||||||
|
.detail-item:nth-last-child(-n+2) { border-bottom: none; }
|
||||||
|
.detail-item:nth-child(odd) { border-right: 1px solid #ebeef5; }
|
||||||
|
.dl {
|
||||||
|
width: 90px; flex-shrink: 0; background: #f5f7fa; padding: 10px 12px;
|
||||||
|
font-size: 12px; color: #606266; font-weight: 600; border-right: 1px solid #ebeef5;
|
||||||
|
}
|
||||||
|
.dv { padding: 10px 12px; font-size: 13px; color: #303133; flex: 1; }
|
||||||
|
.detail-remark {
|
||||||
|
padding: 8px 12px; background: #fdf6ec; border: 1px solid #faecd8;
|
||||||
|
border-radius: 4px; font-size: 12px; color: #e6a23c; margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.section-bar {
|
||||||
|
font-size: 13px; font-weight: 700; color: #1a2c4e;
|
||||||
|
padding: 6px 0 6px 10px; margin-bottom: 10px;
|
||||||
|
border-left: 4px solid #4A6FA5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -124,6 +124,8 @@
|
|||||||
@click="handleAccept(s.row)" v-if="s.row.status==='submitted' && !isSupplier">采纳</el-button>
|
@click="handleAccept(s.row)" v-if="s.row.status==='submitted' && !isSupplier">采纳</el-button>
|
||||||
<el-button size="mini" type="text" style="color:#F56C6C" icon="el-icon-close"
|
<el-button size="mini" type="text" style="color:#F56C6C" icon="el-icon-close"
|
||||||
@click="handleReject(s.row)" v-if="s.row.status==='submitted' && !isSupplier">拒绝</el-button>
|
@click="handleReject(s.row)" v-if="s.row.status==='submitted' && !isSupplier">拒绝</el-button>
|
||||||
|
<el-button size="mini" type="text" style="color:#4A6FA5" icon="el-icon-s-order"
|
||||||
|
@click="handleCreateDelivery(s.row)" v-if="s.row.status==='accepted'">生成发货单</el-button>
|
||||||
<el-button size="mini" type="text" icon="el-icon-delete" style="color:#f56c6c"
|
<el-button size="mini" type="text" icon="el-icon-delete" style="color:#f56c6c"
|
||||||
@click="handleDelete(s.row)" v-if="s.row.status==='draft'">删除</el-button>
|
@click="handleDelete(s.row)" v-if="s.row.status==='draft'">删除</el-button>
|
||||||
</template>
|
</template>
|
||||||
@@ -369,6 +371,7 @@ import { listQuotation, getQuotation, addQuotation, updateQuotation,
|
|||||||
submitQuotation, acceptQuotation, rejectQuotation, delQuotation } from "@/api/bid/quotation";
|
submitQuotation, acceptQuotation, rejectQuotation, delQuotation } from "@/api/bid/quotation";
|
||||||
import { listRfq, getRfqItems } from "@/api/bid/rfq";
|
import { listRfq, getRfqItems } from "@/api/bid/rfq";
|
||||||
import { listSupplier } from "@/api/bid/supplier";
|
import { listSupplier } from "@/api/bid/supplier";
|
||||||
|
import { addDelivery } from "@/api/bid/delivery";
|
||||||
import logoImg from "@/assets/logo/logo.png";
|
import logoImg from "@/assets/logo/logo.png";
|
||||||
import html2canvas from "html2canvas";
|
import html2canvas from "html2canvas";
|
||||||
import jsPDF from "jspdf";
|
import jsPDF from "jspdf";
|
||||||
@@ -504,6 +507,47 @@ export default {
|
|||||||
this.$modal.confirm("确认删除?").then(() => delQuotation(row.quotationId))
|
this.$modal.confirm("确认删除?").then(() => delQuotation(row.quotationId))
|
||||||
.then(() => { this.$modal.msgSuccess("删除成功"); this.getList(); });
|
.then(() => { this.$modal.msgSuccess("删除成功"); this.getList(); });
|
||||||
},
|
},
|
||||||
|
handleCreateDelivery(row) {
|
||||||
|
this.$modal.confirm("确认基于此报价单生成发货单?").then(() => {
|
||||||
|
return getQuotation(row.quotationId);
|
||||||
|
}).then(res => {
|
||||||
|
const q = res.data;
|
||||||
|
if (!q || !q.items || q.items.length === 0) {
|
||||||
|
throw new Error("该报价单无明细,无法生成发货单");
|
||||||
|
}
|
||||||
|
// 计算交货期 = 今天 + 报价单中的交期天数
|
||||||
|
let deliveryDate = ""
|
||||||
|
if (q.deliveryDays) {
|
||||||
|
const d = new Date()
|
||||||
|
d.setDate(d.getDate() + q.deliveryDays)
|
||||||
|
deliveryDate = d.toISOString().slice(0, 10)
|
||||||
|
}
|
||||||
|
const doPayload = {
|
||||||
|
rfqId: q.rfqId,
|
||||||
|
quotationId: q.quotationId,
|
||||||
|
supplierId: q.supplierId,
|
||||||
|
totalAmount: q.totalAmount,
|
||||||
|
deliveryDate: deliveryDate,
|
||||||
|
deliveryStatus: "pending",
|
||||||
|
items: q.items.map(it => ({
|
||||||
|
materialId: it.materialId || 0,
|
||||||
|
materialName: it.materialName,
|
||||||
|
spec: it.spec || "",
|
||||||
|
unit: it.unit || "",
|
||||||
|
quantity: it.quantity || 0,
|
||||||
|
unitPrice: it.unitPrice || 0,
|
||||||
|
totalPrice: it.totalPrice || ((it.quantity || 0) * (it.unitPrice || 0)),
|
||||||
|
remark: it.remark || ""
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
return addDelivery(doPayload);
|
||||||
|
}).then(() => {
|
||||||
|
this.$modal.msgSuccess("发货单已生成");
|
||||||
|
this.getList();
|
||||||
|
}).catch(e => {
|
||||||
|
if (e.message) this.$modal.msgError(e.message);
|
||||||
|
});
|
||||||
|
},
|
||||||
submitForm(mode) {
|
submitForm(mode) {
|
||||||
this.$refs.form.validate(valid => {
|
this.$refs.form.validate(valid => {
|
||||||
if (!valid) return;
|
if (!valid) return;
|
||||||
|
|||||||
Reference in New Issue
Block a user