feat(example): 添加订单生成功能

- 在路由
This commit is contained in:
zxy 2026-04-17 10:10:59 +08:00
parent f174371f0e
commit 940a7a9341
10 changed files with 622 additions and 6 deletions

View File

@ -61,4 +61,52 @@
ORDER BY eo.ord_date DESC, od.id DESC
</select>
<select id="getOrderItemWithOrderByIds" resultType="jnpf.model.order.OrderItemWithOrderVO">
SELECT
od.id AS itemId,
od.sale_ord_id AS saleOrdId,
od.ord_item_status AS ordItemStatus,
od.ord_qty AS ordQty,
od.produce_qty AS produceQty,
(od.ord_qty - IFNULL(od.produce_qty, 0)) AS remainingQty,
od.material_name AS materialName,
od.unit AS unit,
od.spec AS spec,
od.deliveried_qty AS deliveriedQty,
od.material_code AS materialCode,
od.price_tax AS priceTax,
od.material_use AS materialUse,
od.remark AS remark,
od.f_creator_user_id AS creatorUserId,
eo.is_urgent AS isUrgent,
eo.contract_no AS contractNo,
eo.sale_ord_no AS saleOrdNo,
eo.cust_name AS custName,
eo.ord_type AS ordType,
eo.ord_status AS ordStatus,
eo.pro_status AS proStatus,
eo.ord_date AS ordDate,
eo.req_delivery_date AS reqDeliveryDate,
eo.payment_terms AS paymentTerms,
eo.tax_rate AS taxRate,
eo.ord_status AS ordStatus
FROM tso_order_item od
LEFT JOIN tso_order eo ON od.sale_ord_id = eo.id
<where>
AND (od.f_delete_mark IS NULL OR od.f_delete_mark = 0)
AND (eo.f_delete_mark IS NULL OR eo.f_delete_mark = 0)
AND eo.pro_status &lt;&gt; '2'
AND od.ord_item_status NOT IN ('2','9')
AND eo.ord_status = '3'
<if test="orderIds != null and orderIds.size() > 0">
AND od.id IN
<foreach collection="orderIds" item="orderId" open="(" separator="," close=")">
#{orderId}
</foreach>
</if>
</where>
ORDER BY eo.ord_date DESC, od.id DESC
</select>
</mapper>

View File

@ -21,5 +21,7 @@ public interface OrderDetailMapper extends BaseMapper<OrderDetailEntity> {
List<OrderItemWithOrderVO> getOrderItemWithOrder(Page<OrderItemWithOrderVO> page, @Param("pagination") OrderItemWithOrderPagination pagination);
List<OrderItemWithOrderVO> getOrderItemWithOrderByIds(@Param("orderIds") List<String> orderIds);
}

View File

@ -37,4 +37,6 @@ public interface OrderDetailService extends IService<OrderDetailEntity> {
List<OrderItemWithOrderVO> getOrderItemWithOrder(OrderItemWithOrderPagination pagination);
List<OrderItemWithOrderVO> getOrderItemWithOrderByIds(List<String> orderIds);
}

View File

@ -44,4 +44,9 @@ public class OrderDetailServiceImpl extends ServiceImpl<OrderDetailMapper, Order
pagination.setTotal((int) page.getTotal());
return voList;
}
@Override
public List<OrderItemWithOrderVO> getOrderItemWithOrderByIds(List<String> orderIds) {
return this.baseMapper.getOrderItemWithOrderByIds(orderIds);
}
}

View File

@ -76,6 +76,13 @@ public class OrderDetailController {
return ActionResult.success(new ListVO<>(voList));
}
@Operation(summary = "根据订单ID列表查询订单明细不分页")
@PostMapping("/getOrderItemWithOrderByIds")
public ActionResult<ListVO<OrderItemWithOrderVO>> getOrderItemWithOrderByIds(@RequestBody List<String> orderIds) {
List<OrderItemWithOrderVO> voList = orderDetailService.getOrderItemWithOrderByIds(orderIds);
return ActionResult.success(new ListVO<>(voList));
}
/**
* 获取信息
*

View File

@ -5,6 +5,7 @@ import jnpf.base.Pagination;
import lombok.Data;
import java.util.Date;
import java.util.List;
@Data
public class OrderItemWithOrderPagination extends Pagination {
@ -24,4 +25,6 @@ public class OrderItemWithOrderPagination extends Pagination {
@Schema(description = "行状态")
private String ordItemStatus;
private List<String> orderIds;
}

View File

@ -76,6 +76,17 @@ const baseRouter = [{
icon: 'icon-ym icon-ym-generator-link',
}
},
{
path: '/example/tsoorder/generate',
component: (resolve) => require(['@/views/example/tsoorder/generate'], resolve),
name: 'tsoorderGenerate',
meta: {
title: 'tsoorderGenerate',
affix: false,
zhTitle: '生成订单',
icon: 'icon-ym icon-ym-btn-add',
}
},
]
export default baseRouter

View File

@ -0,0 +1,491 @@
<template>
<div class="generate-order-container">
<div class="page-header">
<el-button @click="goBack" class="back-btn">
<i class="el-icon-arrow-left"></i> 返回
</el-button>
<h2 class="page-title">生成订单</h2>
</div>
<div class="main-content">
<div class="section">
<div class="section-header">
<span class="section-title">基础信息</span>
</div>
<div class="section-body">
<el-form :model="baseForm" label-width="120px" class="base-form">
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="订单编号" prop="orderNo">
<el-input disabled placeholder="" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="订单日期" prop="orderDate">
<el-date-picker v-model="baseForm.orderDate" type="date" placeholder="选择日期" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="订单状态" prop="orderStatus">
<el-select v-model="baseForm.orderStatus" placeholder="请选择" disabled>
<el-option label="未下发" value="0" />
<el-option label="已下发" value="1" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="产品名称" prop="materialName">
<el-input v-model="baseForm.materialName" disabled placeholder="产品名称" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="规格型号" prop="spec">
<el-input v-model="baseForm.spec" disabled placeholder="规格型号" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="单位" prop="unit">
<el-input v-model="baseForm.unitText" disabled placeholder="单位" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="计划数量" prop="planQty">
<el-input v-model.number="baseForm.planQty" readonly placeholder="计划数量" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="开始日期" prop="startDate">
<el-date-picker v-model="baseForm.startDate" type="date" placeholder="选择日期" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="结束日期" prop="endDate">
<el-date-picker v-model="baseForm.endDate" type="date" placeholder="选择日期" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="工艺流程" prop="processFlow">
<el-select v-model="baseForm.processFlow" placeholder="请选择">
<el-option label="流程A" value="A" />
<el-option label="流程B" value="B" />
<el-option label="流程C" value="C" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="16">
<el-form-item label="备注" prop="remark">
<el-input v-model="baseForm.remark" type="textarea" :rows="2" placeholder="请输入备注" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item>
<label class="checkbox-label">
<el-checkbox v-model="baseForm.allLines" /> 所有产线
</label>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
</div>
<div class="section">
<div class="section-header">
<span class="section-title">销售订单信息</span>
</div>
<div class="section-body">
<el-table :data="orderList" border :height="200">
<el-table-column prop="itemId" label="序号" align="center" type="index" />
<el-table-column prop="saleOrdNo" label="订单编码" align="center" />
<el-table-column prop="proStatus" label="生产状态" align="center">
<template slot-scope="scope">
{{ getProStatusText(scope.row.proStatus) }}
</template>
</el-table-column>
<el-table-column prop="ordDate" label="下单日期" align="center" :formatter="jnpf.tableDateFormat1" />
<el-table-column prop="ordQty" label="订单数量" align="center" />
<el-table-column prop="produceQty" label="已转生产数量" align="center" />
<el-table-column prop="remainingQty" label="剩余数量" align="center" />
<el-table-column label="转生产数量" align="center">
<template slot-scope="scope">
<el-input
v-model.number="scope.row.planQty"
:max="scope.row.remainingQty"
@input="handlePlanQtyInput(scope.row)"
placeholder="请输入"
/>
</template>
</el-table-column>
<el-table-column prop="ordType" label="订单类型" align="center">
<template slot-scope="scope">
{{ scope.row.ordType === '2' ? '销售订单' : '其他' }}
</template>
</el-table-column>
<el-table-column prop="isUrgent" label="是否急单" align="center">
<template slot-scope="scope">
{{ scope.row.isUrgent === '1' ? '是' : '否' }}
</template>
</el-table-column>
<el-table-column prop="materialName" label="产品名称" align="center" />
<el-table-column prop="spec" label="规格型号" align="center" />
<el-table-column prop="custName" label="客户名称" align="center" />
<el-table-column prop="unit" label="单位" align="center">
<template slot-scope="scope">
{{ getUnitText(scope.row.unit) }}
</template>
</el-table-column>
</el-table>
</div>
</div>
<div class="section">
<div class="section-header" style="display: flex; align-items: center; justify-content: flex-start;">
<span class="section-title">下发产线信息</span>
<el-button type="primary" size="small" @click="addLine" style="margin-left: 30px;">
<i class="el-icon-plus"></i>
</el-button>
</div>
<div class="section-body">
<el-table :data="lineList" border :height="200">
<el-table-column prop="index" label="序号" align="center" type="index" />
<el-table-column prop="lineCode" label="产线编码" align="center">
<template slot-scope="scope">
<el-select v-model="scope.row.lineCode" placeholder="选择产线">
<el-option label="C0001" value="C0001" />
<el-option label="C0002" value="C0002" />
<el-option label="C0003" value="C0003" />
</el-select>
</template>
</el-table-column>
<el-table-column prop="lineName" label="产线名称" align="center">
<template slot-scope="scope">
<el-input v-model="scope.row.lineName" placeholder="产线名称" />
</template>
</el-table-column>
<el-table-column prop="allocateQty" label="分配数量(件)" align="center">
<template slot-scope="scope">
<el-input v-model.number="scope.row.allocateQty" placeholder="分配数量" />
</template>
</el-table-column>
<el-table-column prop="completedQty" label="已完成数量" align="center">
<template slot-scope="scope">
<el-input v-model.number="scope.row.completedQty" placeholder="已完成数量" />
</template>
</el-table-column>
<el-table-column prop="planStartDate" label="计划开工日期" align="center">
<template slot-scope="scope">
<el-date-picker v-model="scope.row.planStartDate" type="date" placeholder="选择日期" />
</template>
</el-table-column>
<el-table-column prop="planEndDate" label="计划完成日期" align="center">
<template slot-scope="scope">
<el-date-picker v-model="scope.row.planEndDate" type="date" placeholder="选择日期" />
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" align="center">
<template slot-scope="scope">
<el-input v-model="scope.row.remark" placeholder="备注" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="80">
<template slot-scope="scope">
<el-button type="text" @click="deleteLine(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</div>
<div class="page-footer">
<el-button type="primary" @click="save">保存</el-button>
<el-button type="primary" @click="submit">下发</el-button>
<el-button @click="goBack">取消</el-button>
</div>
</div>
</template>
<script>
import request from "@/utils/request";
export default {
name: "GenerateOrder",
data() {
return {
baseForm: {
orderNo: "",
orderDate: new Date(),
orderStatus: "0",
materialName: "",
spec: "",
unit: "",
unitText: "",
planQty: null,
startDate: "",
endDate: "",
processFlow: "",
remark: "",
allLines: false
},
orderList: [],
lineList: []
};
},
mounted() {
this.initData();
},
methods: {
initData() {
const dataStr = this.$route.query.data;
if (dataStr) {
try {
const params = JSON.parse(dataStr);
this.baseForm.orderNo = params.saleOrdNo || "";
this.baseForm.materialName = params.materialName || "";
this.baseForm.spec = params.spec || "";
this.orderIds = params.orderIds || [];
this.loadOrderItems();
} catch (e) {
console.error("解析参数失败:", e);
}
}
},
loadOrderItems() {
this.orderLoading = true;
request({
url: "/api/example/orderDetail/getOrderItemWithOrderByIds",
method: "post",
data: this.orderIds
}).then(res => {
if (res.code === 200) {
this.orderList = res.data.list || [];
this.orderList.forEach((item, index) => {
this.$set(this.orderList[index], 'planQty', null);
});
this.calculateTotalQty();
if (this.orderList.length > 0) {
const firstItem = this.orderList[0];
this.baseForm.unit = firstItem.unit || "";
this.baseForm.unitText = this.getUnitText(firstItem.unit);
}
}
this.orderLoading = false;
}).catch(() => {
this.orderLoading = false;
});
},
calculateTotalQty() {
const total = this.orderList.reduce((sum, item) => (item.remainingQty || 0), 0);
this.baseForm.planQty = total;
},
handlePlanQtyInput(row) {
if (row.planQty > row.remainingQty) {
row.planQty = row.remainingQty;
this.$message.warning('转生成数量不能大于剩余数量');
}
if (row.planQty < 0) {
row.planQty = 0;
this.$message.warning('转生成数量不能为负数');
}
this.updateTotalPlanQty();
},
updateTotalPlanQty() {
const total = this.orderList.reduce((sum, item) => sum + (item.planQty || 0), 0);
this.baseForm.planQty = total;
},
getProStatusText(status) {
const map = { '0': '正常', '1': '部分转生产', '2': '全部转生产' };
return map[status] || status;
},
getUnitText(unit) {
const map = { '1': '件', '2': '公斤', '3': '吨' };
return map[unit] || unit;
},
formatDate(row, column) {
const date = row[column.prop];
if (date) {
return new Date(date).toLocaleDateString('zh-CN');
}
return '';
},
addLine() {
this.lineList.push({
lineCode: "",
lineName: "",
allocateQty: null,
completedQty: null,
planStartDate: "",
planEndDate: "",
remark: ""
});
},
deleteLine(index) {
this.lineList.splice(index, 1);
},
goBack() {
this.$router.push("/example/tsoorder");
},
save() {
if (!this.validatePlanQty()) return;
const submitData = this.prepareSubmitData();
request({
url: "/api/example/tsoOrder/save",
method: "post",
data: submitData
}).then(res => {
if (res.code === 200) {
this.$message.success("保存成功");
this.goBack();
}
});
},
submit() {
if (!this.validatePlanQty()) return;
const submitData = this.prepareSubmitData();
submitData.orderStatus = "1";
request({
url: "/api/example/tsoOrder/submit",
method: "post",
data: submitData
}).then(res => {
if (res.code === 200) {
this.$message.success("下发成功");
this.goBack();
}
});
},
validatePlanQty() {
for (let item of this.orderList) {
if (!item.planQty || item.planQty <= 0) {
this.$message.error("转生成数量不能为空且必须大于0");
return false;
}
if (item.planQty > item.remainingQty) {
this.$message.error("转生成数量不能大于剩余数量");
return false;
}
}
return true;
},
prepareSubmitData() {
const orderItems = this.orderList.map(item => ({
itemId: item.itemId,
saleOrdNo: item.saleOrdNo,
planQty: item.planQty,
materialName: item.materialName,
spec: item.spec,
unit: item.unit
}));
return {
...this.baseForm,
orderItems: orderItems
};
}
}
};
</script>
<style scoped>
.generate-order-container {
min-height: 100vh;
background: #f5f7fa;
display: flex;
flex-direction: column;
}
.page-header {
background: #fff;
padding: 16px 24px;
display: flex;
align-items: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.back-btn {
margin-right: 20px;
padding: 6px 16px;
}
.page-title {
font-size: 18px;
font-weight: 600;
color: #303133;
}
.main-content {
flex: 1;
padding: 24px;
overflow-y: auto;
}
.section {
background: #fff;
border-radius: 8px;
margin-bottom: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.section-header {
padding: 16px 20px;
border-bottom: 1px solid #e4e7ed;
display: flex;
justify-content: space-between;
align-items: center;
}
.section-title {
font-size: 14px;
font-weight: 600;
color: #303133;
}
.section-body {
padding: 20px;
}
.base-form {
width: 100%;
}
.checkbox-label {
display: flex;
align-items: center;
font-weight: normal;
}
.page-footer {
background: #fff;
padding: 16px 24px;
display: flex;
justify-content: flex-end;
gap: 16px;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.06);
}
.page-footer el-button {
padding: 8px 24px;
}
</style>

View File

@ -56,6 +56,14 @@
>
重置
</el-button>
<el-button
type="primary"
icon="el-icon-plus"
@click="generateOrder()"
:disabled="!hasSelected"
>
生成订单
</el-button>
</el-form-item>
</el-col>
</el-form>
@ -65,7 +73,8 @@
<div></div>
<div class="JNPF-common-head-right"></div>
</div>
<JNPF-table v-loading="listLoading" :data="list" border ref="table" height="400">
<JNPF-table v-loading="listLoading" :data="list" border ref="table" height="400" :row-key="row => row.itemId" @selection-change="handleSelectionChange">
<el-table-column type="selection" align="center" :reserve-selection="true" />
<el-table-column prop="saleOrdNo" label="订单编码" align="center" min-width="120" />
<el-table-column prop="ordStatus" label="订单状态" align="center" min-width="100">
<template slot-scope="scope">
@ -158,6 +167,8 @@ export default {
ordItemStatusOptions: [],
ordItemStatusProps: { label: "fullName", value: "enCode" },
ordStatusOptions: [],
hasSelected: false,
selectedRows: [],
};
},
computed: {
@ -245,6 +256,47 @@ export default {
this.$message.info("编辑功能开发中");
},
handleSelectionChange(selection) {
this.selectedRows = selection || [];
this.hasSelected = this.selectedRows.length > 0;
},
generateOrder() {
const selectedRows = this.selectedRows;
if (!selectedRows || selectedRows.length === 0) {
this.$message.warning("请选择要生成订单的记录");
return;
}
const materialNames = [...new Set(selectedRows.map(row => row.materialName))];
const specs = [...new Set(selectedRows.map(row => row.spec))];
if (materialNames.length > 1) {
this.$message.error("只能选择同一产品名称的记录生成订单");
return;
}
if (specs.length > 1) {
this.$message.error("只能选择同一规格型号的记录生成订单");
return;
}
const saleOrdNos = [...new Set(selectedRows.map(row => row.saleOrdNo))];
const orderIds = selectedRows.map(row => row.itemId);
const params = {
orderIds: orderIds,
materialName: materialNames[0],
spec: specs[0],
saleOrdNo: saleOrdNos.join(', ')
};
this.$router.push({
path: "/example/tsoorder/generate",
query: { data: JSON.stringify(params) }
});
},
getDictText(value, options) {
if (!value || !options || !options.length) return value;
const item = options.find((opt) => opt.enCode === value || opt.id === value);

View File

@ -88,13 +88,8 @@
<!-- 机台信息显示区域 -->
<div v-if="showMachineInfo" class="JNPF-common-layout-main" style="height: auto; overflow: visible; margin-top: 20px;">
<div class="JNPF-common-head">
<div>
<span >机台信息</span>
</div>
<div class="JNPF-common-head-right">
<el-button type="text" @click="hideMachineInfo" style="color: #999;">收起</el-button>
</div>
</div>
<div style="max-height: 360px; overflow-y: auto;">
<el-table
:data="machineDetailList"