feat(tyre): 新增轮胎报废管理功能,优化维保工单与生命周期展示

1.  新增轮胎报废查询页面与控制器,仅读取PDA落库的报废事实
2.  扩展维保类型枚举,新增抢碎修、小修、轮胎修补/报废类型
3.  重构车辆生命周期里程计算逻辑,修复历史里程显示异常
4.  优化工单详情页渲染,支持空工单空态提示,修复轮位展示逻辑
5.  新增维保前后轮胎快照查询接口,优化工单校验逻辑
6.  完善轮胎生命周期页面,展示报废记录与关联照片
7.  修复订单查询条件与映射逻辑,修正部分页面API路径
master
zch 1 month ago
parent 16a9178708
commit 8314e1b94f

@ -0,0 +1,117 @@
package com.ruoyi.web.controller.tyre;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.system.domain.BizOrderTireDetail;
import com.ruoyi.system.domain.SysAttachment;
import com.ruoyi.system.service.IBizOrderTireDetailService;
import com.ruoyi.system.service.ISysAttachmentService;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* Controller
*
* Web PDA / PDA
*/
@Controller
@RequestMapping("/tyre/scrap")
public class BaseTyreScrapController extends BaseController
{
private String prefix = "tyre/scrap";
@Autowired
private IBizOrderTireDetailService bizOrderTireDetailService;
@Autowired
private ISysAttachmentService sysAttachmentService;
@RequiresPermissions("tyre:scrap:view")
@GetMapping()
public String scrap()
{
return prefix + "/scrap";
}
/**
*
*/
@RequiresPermissions("tyre:scrap:list")
@PostMapping("/list")
@ResponseBody
public TableDataInfo list(BizOrderTireDetail bizOrderTireDetail)
{
normalizeScrapTimeRange(bizOrderTireDetail);
startPage();
List<BizOrderTireDetail> list = bizOrderTireDetailService.selectScrapList(bizOrderTireDetail);
return getDataTable(list);
}
/**
*
*/
@RequiresPermissions("tyre:scrap:export")
@Log(title = "轮胎报废查询", businessType = BusinessType.EXPORT)
@PostMapping("/export")
@ResponseBody
public AjaxResult export(BizOrderTireDetail bizOrderTireDetail)
{
normalizeScrapTimeRange(bizOrderTireDetail);
List<BizOrderTireDetail> list = bizOrderTireDetailService.selectScrapList(bizOrderTireDetail);
ExcelUtil<BizOrderTireDetail> util = new ExcelUtil<>(BizOrderTireDetail.class);
return util.exportExcel(list, "轮胎报废数据");
}
/**
*
*/
@RequiresPermissions("tyre:scrap:list")
@GetMapping("/photos/{orderId}")
@ResponseBody
public AjaxResult photos(@PathVariable("orderId") Long orderId)
{
List<SysAttachment> attachments = sysAttachmentService.selectAttachmentsByOrderId(orderId);
return AjaxResult.success(attachments);
}
private void normalizeScrapTimeRange(BizOrderTireDetail bizOrderTireDetail)
{
if (bizOrderTireDetail == null)
{
return;
}
Map<String, Object> params = bizOrderTireDetail.getParams();
Date beginTime = DateUtils.parseDate(params.get("beginTime"));
if (beginTime != null)
{
params.put("beginTimeDate", beginTime);
}
Date endTime = DateUtils.parseDate(params.get("endTime"));
if (endTime != null)
{
Calendar calendar = Calendar.getInstance();
calendar.setTime(endTime);
calendar.add(Calendar.DATE, 1);
// 使用半开区间规避 MySQL/SQL Server 日期函数差异,并覆盖结束日期当天 23:59:59 的数据。
params.put("endTimeExclusive", calendar.getTime());
}
}
}

@ -25,7 +25,6 @@ import org.springframework.web.multipart.MultipartFile;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -132,23 +131,21 @@ public class BizMaintenanceOrderController extends BaseController {
/** /**
* *
* <p>
* / biz_order_tire_detail
* <ul>
* <li> = </li>
* <li> = + PDA </li>
* </ul>
* status mapper 'removed' / 'installed' / 'normal' CSS
*/ */
@RequiresPermissions("system:order:edit") @RequiresPermissions("system:order:edit")
@GetMapping("/edit/{orderId}") @GetMapping("/edit/{orderId}")
public String edit(@PathVariable("orderId") Long orderId, ModelMap mmap) { public String edit(@PathVariable("orderId") Long orderId, ModelMap mmap) {
BizMaintenanceOrder bizMaintenanceOrder = bizMaintenanceOrderService.selectBizMaintenanceOrderByOrderId(orderId); BizMaintenanceOrder bizMaintenanceOrder = bizMaintenanceOrderService.selectBizMaintenanceOrderByOrderId(orderId);
mmap.put("bizMaintenanceOrder", bizMaintenanceOrder); mmap.put("bizMaintenanceOrder", bizMaintenanceOrder);
// 查询同车牌上一张已完成维保工单;“维保前”按业务口径取上一单的维保后结果,不能再混入当前车辆实时装胎状态。 List<Map> bizOrderTireDetailsBefore = bizOrderTireDetailService.selectBeforeSnapshot(orderId);
BizMaintenanceOrder bizMaintenanceOrderBefore = bizMaintenanceOrderService.selectBizMaintenanceOrderByOrderIdBefore(bizMaintenanceOrder); List<Map> bizOrderTireDetails = bizOrderTireDetailService.selectAfterSnapshot(orderId);
BizOrderTireDetail bizOrderTireDetail = new BizOrderTireDetail();
List<Map> bizOrderTireDetailsBefore = new ArrayList<>();
if (bizMaintenanceOrderBefore != null) {
bizOrderTireDetail.setOrderId(bizMaintenanceOrderBefore.getOrderId());
bizOrderTireDetailsBefore = bizOrderTireDetailService.selectBizOrderTireDetail(bizOrderTireDetail);
}
//查询此车辆的维保数据
bizOrderTireDetail.setOrderId(orderId);
List<Map> bizOrderTireDetails = bizOrderTireDetailService.selectBizOrderTireDetail(bizOrderTireDetail);
mmap.put("bizOrderTireDetailsBefore", bizOrderTireDetailsBefore); mmap.put("bizOrderTireDetailsBefore", bizOrderTireDetailsBefore);
mmap.put("bizOrderTireDetails", bizOrderTireDetails); mmap.put("bizOrderTireDetails", bizOrderTireDetails);
return prefix + "/edit"; return prefix + "/edit";
@ -230,8 +227,24 @@ public class BizMaintenanceOrderController extends BaseController {
BizMaintenanceOrder order = bizMaintenanceOrder.getOrder(); BizMaintenanceOrder order = bizMaintenanceOrder.getOrder();
// 工单类型权威依据typeCode="7" 表示轮胎报废工单NewHomePageActivity 映射),
// 报废页面不采集里程/花纹深度,相关校验需要按工单类型分流,不能依赖 detail.dataType
// 兜底(旧版 APK 可能漏字段)。
boolean isScrapOrder = "7".equals(order.getTypeCode());
// 兜住真正的"空提交":若 PDA 已实时完成装卸胎,允许继续完成工单;若完全没有轮胎动作,则在更新主单前拦截。
// 这样既避免历史脏单(例如 order_id=107也避免用户装胎返回后被误提示“请至少处理一条轮胎”。
List<BizOrderTireDetail> tireDetails = bizMaintenanceOrder.getTireDetails();
boolean hasSubmittedTireDetails = tireDetails != null && !tireDetails.isEmpty();
if (!hasSubmittedTireDetails && (isScrapOrder || !hasRealtimeTyreRecord(order))) {
return error(isScrapOrder
? "请扫描轮胎并选择报废位置后再提交"
: "请至少处理一条轮胎后再完成保养工单");
}
// 报废工单整体跳过里程校验;其它工单仍要求 inputMileage>0。
BigDecimal inputMileage = order.getInputMileage(); BigDecimal inputMileage = order.getInputMileage();
if (inputMileage == null|| inputMileage.compareTo(BigDecimal.ZERO) == 0) { if (!isScrapOrder && (inputMileage == null || inputMileage.compareTo(BigDecimal.ZERO) == 0)) {
return error("请输入里程"); return error("请输入里程");
} }
@ -243,26 +256,32 @@ public class BizMaintenanceOrderController extends BaseController {
// 4. 核心逻辑:只有更新成功(影响行数 > 0才继续执行 // 4. 核心逻辑:只有更新成功(影响行数 > 0才继续执行
if (n > 0) { if (n > 0) {
// 保存详细表 // 保存详细表
if (bizMaintenanceOrder.getTireDetails() != null && bizMaintenanceOrder.getTireDetails().size() > 0) { if (hasSubmittedTireDetails) {
for (BizOrderTireDetail bizOrderTireDetail : bizMaintenanceOrder.getTireDetails()) { for (BizOrderTireDetail bizOrderTireDetail : tireDetails) {
bizOrderTireDetail.setCreateTime(DateUtils.getNowDate()); bizOrderTireDetail.setCreateTime(DateUtils.getNowDate());
bizOrderTireDetail.setOrderId(orderId); bizOrderTireDetail.setOrderId(orderId);
BaseTyre baseTyre = baseTyreService.selectBaseTyreById(bizOrderTireDetail.getTireId()); BaseTyre baseTyre = baseTyreService.selectBaseTyreById(bizOrderTireDetail.getTireId());
recordTyreMileageService.funInsertTyreMileage( // 报废工单整体不写里程历史:报废页面不采集里程/花纹深度,强行写入会 NPE
"保养", // 也会产生不属于报废动作的"保养"里程记录。以工单 typeCode 为主detail
order.getInputMileage().longValue(), // 的 dataType 仅作兜底(避免旧版 APK 漏字段时误写)。
bizOrderTireDetail.getTreadDepth().toString(), boolean isScrapDetail = isScrapOrder || "报废".equals(bizOrderTireDetail.getDataType());
baseTyre.getTyreEpc(), if (!isScrapDetail) {
order.getPlateNumber(), null); recordTyreMileageService.funInsertTyreMileage(
"保养",
order.getInputMileage().longValue(),
bizOrderTireDetail.getTreadDepth().toString(),
baseTyre.getTyreEpc(),
order.getPlateNumber(), null);
}
// 这里如果抛出异常,整个事务会回滚 // 这里如果抛出异常,整个事务会回滚
bizOrderTireDetailService.insertBizOrderTireDetail(bizOrderTireDetail); bizOrderTireDetailService.insertBizOrderTireDetail(bizOrderTireDetail);
} }
} }
if (files != null && files.size() > 0) { if (files != null && !files.isEmpty()) {
//保存图片 //保存图片
for (int i = 0; i < files.size(); i++) { for (int i = 0; i < files.size(); i++) {
try { try {
@ -338,4 +357,27 @@ public class BizMaintenanceOrderController extends BaseController {
bizMaintenanceOrder.getParams().put("beginTime", DateUtils.toDate(today)); bizMaintenanceOrder.getParams().put("beginTime", DateUtils.toDate(today));
bizMaintenanceOrder.getParams().put("endTime", DateUtils.toDate(today.plusDays(1))); bizMaintenanceOrder.getParams().put("endTime", DateUtils.toDate(today.plusDays(1)));
} }
private boolean hasRealtimeTyreRecord(BizMaintenanceOrder submittedOrder) {
if (submittedOrder == null || submittedOrder.getOrderId() == null) {
return false;
}
BizMaintenanceOrder persistedOrder = bizMaintenanceOrderService.selectBizMaintenanceOrderByOrderId(submittedOrder.getOrderId());
if (persistedOrder == null) {
return false;
}
String plateNumber = StringUtils.isEmpty(persistedOrder.getPlateNumber())
? submittedOrder.getPlateNumber()
: persistedOrder.getPlateNumber();
if (StringUtils.isEmpty(plateNumber) || persistedOrder.getCreateTime() == null) {
return false;
}
BizMaintenanceOrder realtimeQuery = new BizMaintenanceOrder();
realtimeQuery.setPlateNumber(plateNumber);
realtimeQuery.setCreateTime(persistedOrder.getCreateTime());
// 装胎/卸胎是实时落库动作;返回工单完成页时明细可能尚未随表单提交,所以按工单创建时间到当前时刻兜底识别。
realtimeQuery.setUpdateTime(DateUtils.getNowDate());
return bizOrderTireDetailService.countRealtimeHandledTyreRecords(realtimeQuery) > 0;
}
} }

@ -42,7 +42,7 @@
</div> </div>
<th:block th:include="include :: footer" /> <th:block th:include="include :: footer" />
<script type="text/javascript"> <script type="text/javascript">
var prefix = ctx + "system/car" var prefix = ctx + "tyre/car"
$("#form-car-add").validate({ $("#form-car-add").validate({
focusCleanup: true focusCleanup: true
}); });

@ -48,7 +48,7 @@
</div> </div>
<th:block th:include="include :: footer" /> <th:block th:include="include :: footer" />
<script type="text/javascript"> <script type="text/javascript">
var prefix = ctx + "system/car"; var prefix = ctx + "tyre/car";
$("#form-car-edit").validate({ $("#form-car-edit").validate({
focusCleanup: true focusCleanup: true
}); });

@ -804,9 +804,15 @@
if (value == "1") { if (value == "1") {
return "二级保养"; return "二级保养";
} }
if (value == "2") {
return "抢碎修";
}
if (value == "4") { if (value == "4") {
return "月检"; return "月检";
} }
if (value == "5") {
return "小修";
}
return displayValue(value); return displayValue(value);
} }

@ -169,6 +169,22 @@
.status-empty .pos-tag { background: #ccc; } .status-empty .pos-tag { background: #ccc; }
.status-empty .tyre-pattern { display: none; } .status-empty .tyre-pattern { display: none; }
/* 整侧空态:仅当后端该侧 detail 为 0 行时使用,避免画 6 个灰色占位
误导排查人员去找数据。这是历史脏单(如 PDA 空提交的工单)的兼容展示。 */
.empty-hint {
text-align: center;
padding: 80px 20px;
color: #888;
font-size: 14px;
line-height: 1.8;
background: #fafbfc;
border: 1px dashed #d0d4d9;
border-radius: 6px;
}
.empty-hint .empty-hint-icon { font-size: 32px; color: #c0c4ca; margin-bottom: 12px; }
.empty-hint .empty-hint-title { font-size: 15px; color: #555; margin-bottom: 6px; font-weight: 500; }
.empty-hint .empty-hint-sub { font-size: 12px; color: #999; }
/* 按钮 */ /* 按钮 */
.btn-xs { padding: 1px 5px; font-size: 12px; border-radius: 3px; } .btn-xs { padding: 1px 5px; font-size: 12px; border-radius: 3px; }
</style> </style>
@ -252,53 +268,23 @@
'左前轮', '右前轮', // 轴1 '左前轮', '右前轮', // 轴1
'左外轮', '左内轮', '右内轮', '右外轮' // 轴2 '左外轮', '左内轮', '右内轮', '右外轮' // 轴2
]; ];
var backendData = /*[[${bizOrderTireDetails}]]*/ null; // 后端固定返回数组(空工单时为 []),用 Array.isArray 判断更稳;
var beforeendData = /*[[${bizOrderTireDetailsBefore}]]*/ null; // 之前的 !data 在空数组场景是巧合通过,遇到 null/未注入才能触发,语义不准确,这里收紧。
var backendData = /*[[${bizOrderTireDetails}]]*/ [];
var beforeendData = /*[[${bizOrderTireDetailsBefore}]]*/ [];
// 检查数据是否为空 if (!Array.isArray(backendData)) {
if (!backendData) { console.error("维保后轮胎数据格式异常", backendData);
console.error("未获取到轮胎数据"); backendData = [];
return;
} }
if (!beforeendData) { if (!Array.isArray(beforeendData)) {
console.error("未获取到轮胎数据"); console.error("维保前轮胎数据格式异常", beforeendData);
return; beforeendData = [];
} }
// 使用新函数处理数据
// 将后端扁平明细按 POSITION_ORDER 摆位,未占用的位置渲染为灰色空位
var formattedData = transformTyreData(backendData); var formattedData = transformTyreData(backendData);
var formattedDataBefore = transformTyreData(beforeendData); var formattedDataBefore = transformTyreData(beforeendData);
// --- 数据格式化 ---
// 假设后端数据结构包含position(位置), brand(品牌), spec(规格), dot, depth(深度)
// 这里需要将后端数据映射为 renderTyres 函数需要的格式
// var formattedData = backendData.map(function(item) {
// // 这里的字段名需要根据你实际返回的 Map 的 Key 进行调整
// // 例如:如果后端返回的是 "tireBrand",这里就写 item.tireBrand
// return {
// pos: item.position || item.pos || '未知', // 轮胎位置 (左前/右后等)
// brand: item.brand || '-', // 品牌
// spec: item.spec || '-', // 规格
// dot: item.dot || '-', // DOT
// depth: (item.depth || '0') + 'mm', // 深度 (加上单位)
// // 逻辑判断:如果深度小于某个值或者有特定标记,标记为 'removed',否则 'normal'
// // 这里简化处理,假设只要数据存在就是 normal或者根据后端字段 item.status 判断
// status: item.status === 'removed' ? 'removed' : 'normal'
// };
// });
// var formattedDataBefore = beforeendData.map(function(item) {
// // 这里的字段名需要根据你实际返回的 Map 的 Key 进行调整
// // 例如:如果后端返回的是 "tireBrand",这里就写 item.tireBrand
// return {
// pos: item.position || item.pos || '未知', // 轮胎位置 (左前/右后等)
// brand: item.brand || '-', // 品牌
// spec: item.spec || '-', // 规格
// dot: item.dot || '-', // DOT
// depth: (item.depth || '0') + 'mm', // 深度 (加上单位)
// // 逻辑判断:如果深度小于某个值或者有特定标记,标记为 'removed',否则 'normal'
// // 这里简化处理,假设只要数据存在就是 normal或者根据后端字段 item.status 判断
// status: item.status || 'normal'
// };
// });
// 渲染函数 // 渲染函数
function renderTyres(data, containerId) { function renderTyres(data, containerId) {
let html = ''; let html = '';
@ -370,9 +356,24 @@
</div> </div>
</div>`; </div>`;
} }
// 执行渲染 // 执行渲染:每侧独立判断,原始 detail 数组为空时改画空态提示,
renderTyres(formattedDataBefore, '#before-container'); // 避免历史脏单(如 PDA 空提交的工单 order_id=107显示 6 个灰色占位让人误以为查询接口坏了。
renderTyres(formattedData, '#after-container'); renderSideOrEmpty(formattedDataBefore, beforeendData, '#before-container', '本次工单未记录维保前明细');
renderSideOrEmpty(formattedData, backendData, '#after-container', '本次工单未记录维保后明细');
function renderSideOrEmpty(formatted, raw, containerId, emptyTitle) {
if (!raw || raw.length === 0) {
$(containerId).html(
'<div class="empty-hint">' +
'<div class="empty-hint-icon"><i class="fa fa-info-circle"></i></div>' +
'<div class="empty-hint-title">' + emptyTitle + '</div>' +
'<div class="empty-hint-sub">请确认 PDA 已对该工单完成轮胎处理动作</div>' +
'</div>'
);
return;
}
renderTyres(formatted, containerId);
}
// --- 工具函数:将后端扁平数据转换为固定长度的数组 --- // --- 工具函数:将后端扁平数据转换为固定长度的数组 ---
function transformTyreData(backendList) { function transformTyreData(backendList) {

@ -18,7 +18,7 @@
<label>维保类型:</label> <label>维保类型:</label>
<select name="typeCode" th:with="type=${@dict.getType('main_type')}"> <select name="typeCode" th:with="type=${@dict.getType('main_type')}">
<option value="">所有</option> <option value="">所有</option>
<option th:each="dict : ${type}" th:if="${dict.dictValue == '1'} or ${dict.dictValue == '4'}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option> <option th:each="dict : ${type}" th:if="${dict.dictValue == '1'} or ${dict.dictValue == '2'} or ${dict.dictValue == '4'} or ${dict.dictValue == '5'}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
</select> </select>
</li> </li>
<!-- <li>--> <!-- <li>-->

@ -0,0 +1,283 @@
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<th:block th:include="include :: header('轮胎报废查询')" />
<style>
.scrap-photo-list {
display: flex;
flex-wrap: wrap;
gap: 10px;
padding: 14px;
}
.scrap-photo-item {
width: 150px;
border: 1px solid #e7eaec;
border-radius: 4px;
background: #fff;
overflow: hidden;
}
.scrap-photo-item img {
width: 150px;
height: 110px;
object-fit: cover;
display: block;
}
.scrap-photo-name {
padding: 6px 8px;
color: #676a6c;
font-size: 12px;
word-break: break-all;
}
.scrap-photo-empty {
padding: 48px 16px;
color: #999;
text-align: center;
}
</style>
</head>
<body class="gray-bg">
<div class="container-div">
<div class="row">
<div class="col-sm-12 search-collapse">
<form id="formId">
<div class="select-list">
<ul>
<li>
<label>工单编号:</label>
<input type="text" name="orderNo"/>
</li>
<li>
<label>车牌号码:</label>
<input type="text" name="plateNumber"/>
</li>
<li>
<label>胎号:</label>
<input type="text" name="tyreNo"/>
</li>
<li>
<label>轮胎编号:</label>
<input type="text" name="tireCode"/>
</li>
<li>
<label>工单类型:</label>
<select name="typeCode" th:with="type=${@dict.getType('main_type')}">
<option value="">所有</option>
<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
</select>
</li>
<li>
<label>工单状态:</label>
<select name="orderStatus" th:with="type=${@dict.getType('order_status')}">
<option value="">所有</option>
<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
</select>
</li>
<li>
<label>维修站点:</label>
<input type="text" name="factoryName"/>
</li>
<li class="select-time">
<label>报废时间: </label>
<input type="text" class="time-input" id="startTime" placeholder="开始时间" name="params[beginTime]"/>
<span>-</span>
<input type="text" class="time-input" id="endTime" placeholder="结束时间" name="params[endTime]"/>
</li>
<li>
<a class="btn btn-primary btn-rounded btn-sm" onclick="$.table.search()"><i class="fa fa-search"></i>&nbsp;搜索</a>
<a class="btn btn-warning btn-rounded btn-sm" onclick="$.form.reset()"><i class="fa fa-refresh"></i>&nbsp;重置</a>
</li>
</ul>
</div>
</form>
</div>
<div class="btn-group-sm" id="toolbar" role="group">
<a class="btn btn-warning" onclick="$.table.exportExcel()" shiro:hasPermission="tyre:scrap:export">
<i class="fa fa-download"></i> 导出
</a>
</div>
<div class="col-sm-12 select-table table-striped">
<table id="bootstrap-table"></table>
</div>
</div>
</div>
<th:block th:include="include :: footer" />
<script th:inline="javascript">
var typeCodeDatas = [[${@dict.getType('main_type')}]];
var statusDatas = [[${@dict.getType('order_status')}]];
var prefix = ctx + "tyre/scrap";
$(function() {
var options = {
url: prefix + "/list",
exportUrl: prefix + "/export",
modalName: "轮胎报废查询",
columns: [{
checkbox: true
},
{
field: 'detailId',
title: '明细ID',
visible: false
},
{
field: 'scrapTime',
title: '报废时间',
sortable: true
},
{
field: 'orderNo',
title: '工单编号'
},
{
field: 'typeCode',
title: '工单类型',
formatter: function(value) {
return $.table.selectDictLabel(typeCodeDatas, value);
}
},
{
field: 'orderStatus',
title: '工单状态',
formatter: function(value) {
return $.table.selectDictLabel(statusDatas, value);
}
},
{
field: 'plateNumber',
title: '车牌号码'
},
{
field: 'tyreNo',
title: '胎号',
formatter: function(value, row) {
var text = $.common.isEmpty(value) ? row.tireCode : value;
if ($.common.isEmpty(row.tireId)) {
return $.common.nullToStr(text);
}
return '<a href="javascript:void(0)" onclick="openTyreDetail(\'' + row.tireId + '\')">' + escapeHtml(text) + '</a>';
}
},
{
field: 'selfNo',
title: '自编号'
},
{
field: 'tyreBrand',
title: '品牌'
},
{
field: 'tyreModel',
title: '规格'
},
{
field: 'positionName',
title: '轮位'
},
{
field: 'treadDepth',
title: '报废前花纹',
formatter: function(value) {
return $.common.isEmpty(value) ? '-' : value + ' mm';
}
},
{
field: 'inputMileage',
title: '报废时里程',
formatter: function(value) {
return $.common.isEmpty(value) ? '-' : value + ' km';
}
},
{
field: 'factoryName',
title: '维修站点'
},
{
field: 'photoCount',
title: '照片',
formatter: function(value, row) {
var count = value == null ? 0 : value;
if (count === 0 || $.common.isEmpty(row.orderId)) {
return '<span class="text-muted">0 张</span>';
}
return '<a href="javascript:void(0)" onclick="openScrapPhotos(\'' + row.orderId + '\')">' + count + ' 张</a>';
}
},
{
field: 'createBy',
title: '操作人'
},
{
field: 'remark',
title: '备注'
}]
};
$.table.init(options);
});
function openTyreDetail(tireId) {
// 复用现有生命周期详情页,列表页不再重复实现轮胎全生命周期展示逻辑。
$.modal.openTab("轮胎详情", ctx + "tyre/tyre/detail2?tyreId=" + encodeURIComponent(tireId));
}
function openScrapPhotos(orderId) {
$.get(prefix + "/photos/" + encodeURIComponent(orderId), function(result) {
if (result.code !== web_status.SUCCESS) {
$.modal.alertWarning(result.msg);
return;
}
showPhotoLayer(result.data || []);
});
}
function showPhotoLayer(photos) {
var html;
if (!photos || photos.length === 0) {
html = '<div class="scrap-photo-empty">该报废工单暂无照片</div>';
} else {
html = '<div class="scrap-photo-list">';
for (var i = 0; i < photos.length; i++) {
var photo = photos[i] || {};
var url = buildFileUrl(photo.filePath);
html += '<a class="scrap-photo-item" href="' + url + '" target="_blank" title="' + escapeHtml(photo.fileName) + '">'
+ '<img src="' + url + '" alt="报废照片"/>'
+ '<div class="scrap-photo-name">' + escapeHtml(photo.fileName || "报废照片") + '</div>'
+ '</a>';
}
html += '</div>';
}
top.layer.open({
type: 1,
title: '该报废工单照片',
area: ['840px', '560px'],
shade: 0.3,
content: html
});
}
function buildFileUrl(filePath) {
if ($.common.isEmpty(filePath)) {
return '#';
}
if (filePath.indexOf('http://') === 0 || filePath.indexOf('https://') === 0) {
return filePath;
}
// FileUploadUtils 返回 /profile/... 形式;拼 ctx 时去掉开头斜杠以兼容非根上下文部署。
return filePath.charAt(0) === '/' ? ctx + filePath.substring(1) : ctx + filePath;
}
function escapeHtml(value) {
return String(value == null ? "" : value)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
</script>
</body>
</html>

@ -242,6 +242,21 @@
border-top: 0; border-top: 0;
} }
.scrap-photo-grid {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 6px;
}
.scrap-photo-grid img {
width: 64px;
height: 64px;
object-fit: cover;
border: 1px solid #e7eaec;
border-radius: 4px;
}
.empty-life { .empty-life {
padding: 48px 16px; padding: 48px 16px;
color: #999; color: #999;
@ -284,7 +299,9 @@
totalMileage=${resultMap['totalMileage']}, totalMileage=${resultMap['totalMileage']},
currentPatternDepth=${resultMap['currentPatternDepth']}, currentPatternDepth=${resultMap['currentPatternDepth']},
bizMaintenanceOrder=${resultMap['bizMaintenanceOrder']}, bizMaintenanceOrder=${resultMap['bizMaintenanceOrder']},
bizMaintenanceOrderList=${resultMap['bizMaintenanceOrderList']}"> bizMaintenanceOrderList=${resultMap['bizMaintenanceOrderList']},
scrapList=${resultMap['scrapList']},
scrapPhotoMap=${resultMap['scrapPhotoMap']}">
<div class="summary-panel"> <div class="summary-panel">
<div class="summary-main"> <div class="summary-main">
<div> <div>
@ -391,6 +408,7 @@
<span class="life-count" th:text="${'里程使用记录:' + (#lists.isEmpty(recordTyreMileageList) ? 0 : #lists.size(recordTyreMileageList))}">里程使用记录0</span> <span class="life-count" th:text="${'里程使用记录:' + (#lists.isEmpty(recordTyreMileageList) ? 0 : #lists.size(recordTyreMileageList))}">里程使用记录0</span>
<span class="life-count" th:text="${'保养记录:' + (#lists.isEmpty(maintenanceMileageList) ? 0 : #lists.size(maintenanceMileageList))}">保养记录0</span> <span class="life-count" th:text="${'保养记录:' + (#lists.isEmpty(maintenanceMileageList) ? 0 : #lists.size(maintenanceMileageList))}">保养记录0</span>
<span class="life-count" th:text="${'维保工单:' + (#lists.isEmpty(bizMaintenanceOrderList) ? 0 : #lists.size(bizMaintenanceOrderList))}">维保工单0</span> <span class="life-count" th:text="${'维保工单:' + (#lists.isEmpty(bizMaintenanceOrderList) ? 0 : #lists.size(bizMaintenanceOrderList))}">维保工单0</span>
<span class="life-count" th:text="${'报废记录:' + (#lists.isEmpty(scrapList) ? 0 : #lists.size(scrapList))}">报废记录0</span>
</div> </div>
<div class="life-section" th:if="${!#lists.isEmpty(recordWarehousingList)}"> <div class="life-section" th:if="${!#lists.isEmpty(recordWarehousingList)}">
@ -503,6 +521,9 @@
<span th:case="'3'">拆报废车</span> <span th:case="'3'">拆报废车</span>
<span th:case="'4'">月检</span> <span th:case="'4'">月检</span>
<span th:case="'5'">小修</span> <span th:case="'5'">小修</span>
<!-- 与 PDA 端工单类型保持一致,避免报废/修补工单在生命周期中只显示裸码值。 -->
<span th:case="'6'">轮胎修补</span>
<span th:case="'7'">轮胎报废</span>
<span th:case="*" th:text="${#strings.isEmpty(bizMaintenanceOrder.typeCode) ? '维保工单' : bizMaintenanceOrder.typeCode}">维保工单</span> <span th:case="*" th:text="${#strings.isEmpty(bizMaintenanceOrder.typeCode) ? '维保工单' : bizMaintenanceOrder.typeCode}">维保工单</span>
</span> </span>
<span th:if="${!#strings.isEmpty(bizMaintenanceOrder.orderNo)}" th:text="${'' + bizMaintenanceOrder.orderNo}">-</span> <span th:if="${!#strings.isEmpty(bizMaintenanceOrder.orderNo)}" th:text="${'' + bizMaintenanceOrder.orderNo}">-</span>
@ -550,7 +571,60 @@
</ul> </ul>
</div> </div>
<div class="empty-life" th:if="${#lists.isEmpty(recordWarehousingList) and #lists.isEmpty(recordTyreInstallList) and #lists.isEmpty(recordTyreMileageList) and #lists.isEmpty(maintenanceMileageList) and #lists.isEmpty(bizMaintenanceOrderList)}"> <div class="life-section" th:if="${!#lists.isEmpty(scrapList)}">
<div class="life-section-title">报废记录</div>
<ul class="life-list">
<li class="life-item is-uninstall" th:each="scrap : ${scrapList}">
<span class="life-dot"></span>
<div class="life-title-row">
<div class="life-title">
<i class="fa fa-ban"></i>
轮胎报废
<span th:if="${!#strings.isEmpty(scrap.orderNo)}" th:text="${'' + scrap.orderNo}">-</span>
</div>
<div class="life-time" th:text="${scrap.scrapTime == null ? '-' : #dates.format(scrap.scrapTime, 'yyyy-MM-dd HH:mm:ss')}">-</div>
</div>
<div class="life-meta">
<div th:text="${#strings.isEmpty(scrap.plateNumber) ? '车牌号:-' : '车牌号:' + scrap.plateNumber}">车牌号:-</div>
<div th:text="${#strings.isEmpty(scrap.positionName) ? '轮位:-' : '轮位:' + scrap.positionName}">轮位:-</div>
<div th:text="${scrap.treadDepth == null ? '报废前花纹:-' : '报废前花纹:' + scrap.treadDepth + ' mm'}">报废前花纹:-</div>
<div th:text="${scrap.inputMileage == null ? '当时车辆里程:-' : '当时车辆里程:' + scrap.inputMileage + ' km'}">当时车辆里程:-</div>
<div th:text="${#strings.isEmpty(scrap.factoryName) ? '维修站点:-' : '维修站点:' + scrap.factoryName}">维修站点:-</div>
<div th:text="${#strings.isEmpty(scrap.createBy) ? '操作人:-' : '操作人:' + scrap.createBy}">操作人:-</div>
<div th:if="${!#strings.isEmpty(scrap.tireCode)}" th:text="${'轮胎编号:' + scrap.tireCode}">轮胎编号:-</div>
<div>
<span>工单状态:</span>
<span th:switch="${scrap.orderStatus}">
<span th:case="'UNSTARTED'">未开始</span>
<span th:case="'PROCESSING'">执行中</span>
<span th:case="'COMPLETED'">已完成</span>
<span th:case="*" th:text="${#strings.isEmpty(scrap.orderStatus) ? '-' : scrap.orderStatus}">-</span>
</span>
</div>
<div class="life-remark" th:if="${!#strings.isEmpty(scrap.remark)}" th:text="${'备注:' + scrap.remark}">备注:-</div>
<th:block th:with="photos=${scrapPhotoMap == null ? null : scrapPhotoMap.get(scrap.orderId)}">
<div class="life-remark" th:if="${photos != null and !#lists.isEmpty(photos)}">
<div th:text="${'该报废工单照片(' + #lists.size(photos) + ' 张):'}">该报废工单照片0 张):</div>
<div class="scrap-photo-grid">
<!-- PDA 只按 order_id 保存图片,照片角色未入库;这里按上传记录平铺,不伪造“胎号/病象”等标签。 -->
<a class="js-scrap-photo-link"
th:each="photo : ${photos}"
href="#"
target="_blank"
th:attr="data-file-path=${photo.filePath}"
th:title="${photo.fileName}">
<img src="#" alt="报废照片"/>
</a>
</div>
</div>
</th:block>
</div>
</li>
</ul>
</div>
<div class="empty-life" th:if="${#lists.isEmpty(recordWarehousingList) and #lists.isEmpty(recordTyreInstallList) and #lists.isEmpty(recordTyreMileageList) and #lists.isEmpty(maintenanceMileageList) and #lists.isEmpty(bizMaintenanceOrderList) and #lists.isEmpty(scrapList)}">
暂无生命周期记录 暂无生命周期记录
</div> </div>
</div> </div>
@ -562,7 +636,26 @@
if (window.parent && window.parent !== window && window.parent.$ && window.parent.$.modal) { if (window.parent && window.parent !== window && window.parent.$ && window.parent.$.modal) {
window.parent.$.modal.closeLoading(); window.parent.$.modal.closeLoading();
} }
$('.js-scrap-photo-link').each(function () {
var $link = $(this);
var url = buildLifecycleFileUrl($link.attr('data-file-path'));
// FileUploadUtils 返回 /profile/...;生命周期页同样拼 ctx避免部署在非根路径时图片 404。
$link.attr('href', url);
$link.find('img').attr('src', url);
});
}); });
function buildLifecycleFileUrl(filePath) {
if ($.common.isEmpty(filePath)) {
return '#';
}
if (filePath.indexOf('http://') === 0 || filePath.indexOf('https://') === 0) {
return filePath;
}
filePath = filePath.replace(/\\/g, '/');
return filePath.charAt(0) === '/' ? ctx + filePath.substring(1) : ctx + filePath;
}
</script> </script>
</body> </body>
</html> </html>

@ -1,6 +1,8 @@
package com.ruoyi.system.domain; package com.ruoyi.system.domain;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.annotation.Excel;
@ -51,6 +53,64 @@ public class BizOrderTireDetail extends BaseEntity
// 数据类型(保养;换新胎,卸车;换新胎,装车) // 数据类型(保养;换新胎,卸车;换新胎,装车)
private String dataType; private String dataType;
/** 报废执行时间,查询侧按工单日期优先、明细创建时间兜底 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Excel(name = "报废执行时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date scrapTime;
/** 报废工单编号 */
@Excel(name = "工单编号")
private String orderNo;
/** 工单车辆号/轮胎工单号快照 */
@Excel(name = "车牌号码")
private String plateNumber;
/** 维保类型编码PDA 报废工单通常为 7 */
@Excel(name = "工单类型")
private String typeCode;
/** 工单状态 */
@Excel(name = "工单状态")
private String orderStatus;
/** 维修站点/修理厂名称 */
@Excel(name = "维修站点")
private String factoryName;
/** 报废时车辆里程 */
@Excel(name = "报废时车辆里程")
private BigDecimal inputMileage;
/** 工单计划日期 */
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
@Excel(name = "工单日期", width = 30, dateFormat = "yyyy-MM-dd")
private Date maintainDate;
/** 轮胎档案胎号 */
@Excel(name = "档案胎号")
private String tyreNo;
/** 轮胎芯片 */
@Excel(name = "轮胎芯片")
private String tyreEpc;
/** 自编号 */
@Excel(name = "自编号")
private String selfNo;
/** 轮胎品牌 */
@Excel(name = "轮胎品牌")
private String tyreBrand;
/** 轮胎规格 */
@Excel(name = "轮胎规格")
private String tyreModel;
/** 同一报废工单下的照片数量,照片按 sys_attachment.order_id 关联 */
@Excel(name = "照片张数")
private Long photoCount;
public String getPositionName() { public String getPositionName() {
return positionName; return positionName;
} }
@ -147,6 +207,146 @@ public class BizOrderTireDetail extends BaseEntity
return tireStatus; return tireStatus;
} }
public Date getScrapTime()
{
return scrapTime;
}
public void setScrapTime(Date scrapTime)
{
this.scrapTime = scrapTime;
}
public String getOrderNo()
{
return orderNo;
}
public void setOrderNo(String orderNo)
{
this.orderNo = orderNo;
}
public String getPlateNumber()
{
return plateNumber;
}
public void setPlateNumber(String plateNumber)
{
this.plateNumber = plateNumber;
}
public String getTypeCode()
{
return typeCode;
}
public void setTypeCode(String typeCode)
{
this.typeCode = typeCode;
}
public String getOrderStatus()
{
return orderStatus;
}
public void setOrderStatus(String orderStatus)
{
this.orderStatus = orderStatus;
}
public String getFactoryName()
{
return factoryName;
}
public void setFactoryName(String factoryName)
{
this.factoryName = factoryName;
}
public BigDecimal getInputMileage()
{
return inputMileage;
}
public void setInputMileage(BigDecimal inputMileage)
{
this.inputMileage = inputMileage;
}
public Date getMaintainDate()
{
return maintainDate;
}
public void setMaintainDate(Date maintainDate)
{
this.maintainDate = maintainDate;
}
public String getTyreNo()
{
return tyreNo;
}
public void setTyreNo(String tyreNo)
{
this.tyreNo = tyreNo;
}
public String getTyreEpc()
{
return tyreEpc;
}
public void setTyreEpc(String tyreEpc)
{
this.tyreEpc = tyreEpc;
}
public String getSelfNo()
{
return selfNo;
}
public void setSelfNo(String selfNo)
{
this.selfNo = selfNo;
}
public String getTyreBrand()
{
return tyreBrand;
}
public void setTyreBrand(String tyreBrand)
{
this.tyreBrand = tyreBrand;
}
public String getTyreModel()
{
return tyreModel;
}
public void setTyreModel(String tyreModel)
{
this.tyreModel = tyreModel;
}
public Long getPhotoCount()
{
return photoCount;
}
public void setPhotoCount(Long photoCount)
{
this.photoCount = photoCount;
}
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
@ -158,6 +358,20 @@ public class BizOrderTireDetail extends BaseEntity
.append("treadDepth", getTreadDepth()) .append("treadDepth", getTreadDepth())
.append("tirePress", getTirePress()) .append("tirePress", getTirePress())
.append("tireStatus", getTireStatus()) .append("tireStatus", getTireStatus())
.append("scrapTime", getScrapTime())
.append("orderNo", getOrderNo())
.append("plateNumber", getPlateNumber())
.append("typeCode", getTypeCode())
.append("orderStatus", getOrderStatus())
.append("factoryName", getFactoryName())
.append("inputMileage", getInputMileage())
.append("maintainDate", getMaintainDate())
.append("tyreNo", getTyreNo())
.append("tyreEpc", getTyreEpc())
.append("selfNo", getSelfNo())
.append("tyreBrand", getTyreBrand())
.append("tyreModel", getTyreModel())
.append("photoCount", getPhotoCount())
.append("createBy", getCreateBy()) .append("createBy", getCreateBy())
.append("createTime", getCreateTime()) .append("createTime", getCreateTime())
.append("updateBy", getUpdateBy()) .append("updateBy", getUpdateBy())

@ -3,6 +3,7 @@ package com.ruoyi.system.mapper;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.ruoyi.system.domain.BizMaintenanceOrder;
import com.ruoyi.system.domain.BizOrderTireDetail; import com.ruoyi.system.domain.BizOrderTireDetail;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
@ -64,8 +65,52 @@ public interface BizOrderTireDetailMapper
List<Map> selectBizOrderTireDetail(BizOrderTireDetail bizOrderTireDetail); List<Map> selectBizOrderTireDetail(BizOrderTireDetail bizOrderTireDetail);
/**
*
* <p>
*
* status '' -> 'removed''' -> 'normal'
*
* @param orderId ID
* @return
*/
List<Map> selectBeforeSnapshot(@Param("orderId") Long orderId);
/**
*
* <p>
* PDA "维保完成后"
* status ''/ -> 'installed''' -> 'normal'
*
* @param orderId ID
* @return
*/
List<Map> selectAfterSnapshot(@Param("orderId") Long orderId);
List<Map> selectBaseTrieInstall(String plateNumber); List<Map> selectBaseTrieInstall(String plateNumber);
/**
* PDA
* <p>
* / record_tyre_install
*
*
* @param bizMaintenanceOrder
* @return
*/
int countRealtimeHandledTyreRecords(BizMaintenanceOrder bizMaintenanceOrder);
/**
*
* <p>
* PDA tire_status=''
* Web
*
* @param bizOrderTireDetail
* @return
*/
List<BizOrderTireDetail> selectScrapList(BizOrderTireDetail bizOrderTireDetail);
/** /**
* Service * Service
* *

@ -3,6 +3,7 @@ package com.ruoyi.system.service;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.ruoyi.system.domain.BizMaintenanceOrder;
import com.ruoyi.system.domain.BizOrderTireDetail; import com.ruoyi.system.domain.BizOrderTireDetail;
/** /**
@ -63,5 +64,44 @@ public interface IBizOrderTireDetailService
List<Map> selectBizOrderTireDetail(BizOrderTireDetail bizOrderTireDetail); List<Map> selectBizOrderTireDetail(BizOrderTireDetail bizOrderTireDetail);
/**
*
* <p>
* "维保开始时"
* brand / spec / dot / depth / position / status status mapper
* 'removed' 'normal'
*
* @param orderId ID
* @return
*/
List<Map> selectBeforeSnapshot(Long orderId);
/**
*
* <p>
* PDA "维保完成后"
* {@link #selectBeforeSnapshot}status 'installed' 'normal'
*
* @param orderId ID
* @return
*/
List<Map> selectAfterSnapshot(Long orderId);
List<Map> selectBaseTrieInstall(String plateNumber); List<Map> selectBaseTrieInstall(String plateNumber);
/**
* PDA
*
* @param bizMaintenanceOrder
* @return
*/
int countRealtimeHandledTyreRecords(BizMaintenanceOrder bizMaintenanceOrder);
/**
*
*
* @param bizOrderTireDetail
* @return
*/
List<BizOrderTireDetail> selectScrapList(BizOrderTireDetail bizOrderTireDetail);
} }

@ -16,9 +16,9 @@ import com.ruoyi.system.domain.CarLifecycleQuery;
import com.ruoyi.system.domain.CarLifecycleSummaryDTO; import com.ruoyi.system.domain.CarLifecycleSummaryDTO;
import com.ruoyi.system.domain.CarMaintenanceLifecycleDTO; import com.ruoyi.system.domain.CarMaintenanceLifecycleDTO;
import com.ruoyi.system.domain.CarMileageLifecycleDTO; import com.ruoyi.system.domain.CarMileageLifecycleDTO;
import com.ruoyi.system.domain.CarMountedTyreDTO;
import com.ruoyi.system.domain.CarTyreInstallLifecycleDTO; import com.ruoyi.system.domain.CarTyreInstallLifecycleDTO;
import com.ruoyi.system.mapper.BaseCarLifecycleMapper; import com.ruoyi.system.mapper.BaseCarLifecycleMapper;
import com.ruoyi.system.mapper.BizOrderTireDetailMapper;
import com.ruoyi.system.service.IBaseCarLifecycleService; import com.ruoyi.system.service.IBaseCarLifecycleService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -42,9 +42,6 @@ public class BaseCarLifecycleServiceImpl implements IBaseCarLifecycleService
@Autowired @Autowired
private BaseCarLifecycleMapper baseCarLifecycleMapper; private BaseCarLifecycleMapper baseCarLifecycleMapper;
@Autowired
private BizOrderTireDetailMapper bizOrderTireDetailMapper;
/** /**
* *
* <p> * <p>
@ -108,8 +105,8 @@ public class BaseCarLifecycleServiceImpl implements IBaseCarLifecycleService
/** /**
* 6 * 6
* <p> * <p>
* SQL * base_tyre
* Java SQL 使 TOP / LIMIT / ROW_NUMBER * Java
* </p> * </p>
*/ */
@Override @Override
@ -117,19 +114,20 @@ public class BaseCarLifecycleServiceImpl implements IBaseCarLifecycleService
public List<Map> selectLatestWheelPositionMaintenance(CarLifecycleQuery query) public List<Map> selectLatestWheelPositionMaintenance(CarLifecycleQuery query)
{ {
buildAuthorizedCar(query); buildAuthorizedCar(query);
List<Map> detailRows = bizOrderTireDetailMapper.selectCarMaintenanceTireDetails(query.getCarNo()); List<CarMountedTyreDTO> mountedTyres = baseCarLifecycleMapper.selectMountedTyres(query);
Map<String, Map> latestByPosition = new LinkedHashMap<>(); Map<String, Map> latestByPosition = new LinkedHashMap<>();
Set<String> expectedPositions = new HashSet<>(WHEEL_POSITION_ORDER); Set<String> expectedPositions = new HashSet<>(WHEEL_POSITION_ORDER);
if (detailRows != null) if (mountedTyres != null)
{ {
for (Map row : detailRows) for (CarMountedTyreDTO tyre : mountedTyres)
{ {
String position = row == null || row.get("position") == null ? null : String.valueOf(row.get("position")); String position = tyre == null ? null : tyre.getWheelPostion();
if (!expectedPositions.contains(position) || latestByPosition.containsKey(position)) if (!expectedPositions.contains(position) || latestByPosition.containsKey(position))
{ {
continue; continue;
} }
latestByPosition.put(position, row); // 轮位视图展示“当前装车事实”,以 base_tyre 实时轮位为准,避免历史工单明细覆盖 PDA 刚完成的装胎结果。
latestByPosition.put(position, buildMountedTyreRow(tyre));
} }
} }
@ -153,6 +151,34 @@ public class BaseCarLifecycleServiceImpl implements IBaseCarLifecycleService
return result; return result;
} }
private Map buildMountedTyreRow(CarMountedTyreDTO tyre)
{
Map row = new LinkedHashMap();
row.put("position", tyre.getWheelPostion());
row.put("brand", tyre.getTyreBrand());
row.put("spec", tyre.getTyreModel());
row.put("dot", firstNonBlank(tyre.getTyreNo(), tyre.getSelfNo(), tyre.getTyreEpc()));
row.put("depth", tyre.getPatternDepth());
row.put("status", "installed");
return row;
}
private String firstNonBlank(String... values)
{
if (values == null)
{
return null;
}
for (String value : values)
{
if (!StringUtils.isBlank(value))
{
return value;
}
}
return null;
}
/** /**
* / * /
* <p> * <p>

@ -18,6 +18,7 @@ import com.ruoyi.system.mapper.BaseTyreMapper;
import com.ruoyi.system.mapper.RecordTyreInstallMapper; import com.ruoyi.system.mapper.RecordTyreInstallMapper;
import com.ruoyi.system.mapper.RecordWarehousingMapper; import com.ruoyi.system.mapper.RecordWarehousingMapper;
import com.ruoyi.system.service.IBaseTyreService; import com.ruoyi.system.service.IBaseTyreService;
import com.ruoyi.system.service.ISysAttachmentService;
import com.ruoyi.system.util.TyreLifecycleCalc; import com.ruoyi.system.util.TyreLifecycleCalc;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -55,6 +56,9 @@ public class BaseTyreServiceImpl implements IBaseTyreService
private BizMaintenanceOrderMapper bizMaintenanceOrderMapper; private BizMaintenanceOrderMapper bizMaintenanceOrderMapper;
private static final Logger log = LoggerFactory.getLogger(BaseTyreServiceImpl.class); private static final Logger log = LoggerFactory.getLogger(BaseTyreServiceImpl.class);
@Autowired
private ISysAttachmentService sysAttachmentService;
@Autowired @Autowired
private RecordTyreMileageMapper recordTyreMileageMapper; private RecordTyreMileageMapper recordTyreMileageMapper;
@ -302,11 +306,14 @@ public class BaseTyreServiceImpl implements IBaseTyreService
for (BizOrderTireDetail biz : bizOrderTireDetailList){ for (BizOrderTireDetail biz : bizOrderTireDetailList){
//查询维修单//筛选工单 //查询维修单//筛选工单
List<BizOrderTireDetail> filteredList = bizOrderTireDetailList.stream() List<BizOrderTireDetail> filteredList = bizOrderTireDetailList.stream()
.filter(detail -> detail.getOrderId() == biz.getOrderId()) .filter(detail -> Objects.equals(detail.getOrderId(), biz.getOrderId()))
.collect(Collectors.toList()); .collect(Collectors.toList());
BizMaintenanceOrder bizMaintenanceOrder = bizMaintenanceOrderMapper.selectBizMaintenanceOrderByOrderId(biz.getOrderId()); BizMaintenanceOrder bizMaintenanceOrder = bizMaintenanceOrderMapper.selectBizMaintenanceOrderByOrderId(biz.getOrderId());
if (bizMaintenanceOrder == null) {
// 历史明细可能残留已删除/未同步的工单IDWeb详情以可追溯数据为准缺主单时跳过避免一条脏明细拖垮整条生命周期。
continue;
}
bizMaintenanceOrder.setBizOrderTireDetailList(filteredList); bizMaintenanceOrder.setBizOrderTireDetailList(filteredList);
System.out.println(bizMaintenanceOrder.getLastMileage());
if (bizMaintenanceOrder.getLastMileage() ==null || String.valueOf(bizMaintenanceOrder.getLastMileage() ).equals("null")) { if (bizMaintenanceOrder.getLastMileage() ==null || String.valueOf(bizMaintenanceOrder.getLastMileage() ).equals("null")) {
bizMaintenanceOrder.setLastMileage(BigDecimal.valueOf(0)); bizMaintenanceOrder.setLastMileage(BigDecimal.valueOf(0));
} }
@ -333,6 +340,8 @@ public class BaseTyreServiceImpl implements IBaseTyreService
map.put("recordTyreMileageList", new ArrayList<>()); map.put("recordTyreMileageList", new ArrayList<>());
map.put("maintenanceMileageList", new ArrayList<>()); map.put("maintenanceMileageList", new ArrayList<>());
map.put("bizMaintenanceOrderList", new ArrayList<>()); map.put("bizMaintenanceOrderList", new ArrayList<>());
map.put("scrapList", new ArrayList<>());
map.put("scrapPhotoMap", new LinkedHashMap<>());
map.put("totalMileage", null); map.put("totalMileage", null);
map.put("currentPatternDepth", null); map.put("currentPatternDepth", null);
@ -345,6 +354,8 @@ public class BaseTyreServiceImpl implements IBaseTyreService
// 5. 提取轮胎实体和 RFIDEPC编码 // 5. 提取轮胎实体和 RFIDEPC编码
BaseTyre resultBase = (BaseTyre) resultBaseObj; BaseTyre resultBase = (BaseTyre) resultBaseObj;
// 报废事实按轮胎 ID 关联工单明细,即使历史胎缺 RFID也应优先把 Web 可追溯的报废记录展示出来。
putScrapList(map, resultBase);
String tyreRfid = resultBase.getTyreEpc(); String tyreRfid = resultBase.getTyreEpc();
// 6. RFID 为空时清空流转列表并返回,避免展示错误数据 // 6. RFID 为空时清空流转列表并返回,避免展示错误数据
if (StringUtils.isBlank(tyreRfid)) if (StringUtils.isBlank(tyreRfid))
@ -558,6 +569,42 @@ public class BaseTyreServiceImpl implements IBaseTyreService
map.put("bizMaintenanceOrder", orderList.isEmpty() ? null : orderList.get(0)); map.put("bizMaintenanceOrder", orderList.isEmpty() ? null : orderList.get(0));
} }
private void putScrapList(Map map, BaseTyre resultBase)
{
if (resultBase.getTyreId() == null)
{
return;
}
BizOrderTireDetail query = new BizOrderTireDetail();
query.setTireId(resultBase.getTyreId());
List<BizOrderTireDetail> scrapList = bizOrderTireDetailMapper.selectScrapList(query);
map.put("scrapList", scrapList == null ? new ArrayList<>() : scrapList);
Map<Long, List<SysAttachment>> scrapPhotoMap = new LinkedHashMap<>();
if (scrapList == null || scrapList.isEmpty())
{
map.put("scrapPhotoMap", scrapPhotoMap);
return;
}
Set<Long> seenOrderIds = new LinkedHashSet<>();
for (BizOrderTireDetail scrap : scrapList)
{
if (scrap == null || scrap.getOrderId() == null || !seenOrderIds.add(scrap.getOrderId()))
{
continue;
}
// PDA 图片只按 order_id 入附件表同一报废工单多条轮胎共享同一组照片Web 端不能伪造成单胎照片。
List<SysAttachment> attachments = sysAttachmentService.selectAttachmentsByOrderId(scrap.getOrderId());
if (attachments != null && !attachments.isEmpty())
{
scrapPhotoMap.put(scrap.getOrderId(), attachments);
}
}
map.put("scrapPhotoMap", scrapPhotoMap);
}
private Date maintenanceRecordTime(RecordTyreMileage item) private Date maintenanceRecordTime(RecordTyreMileage item)
{ {
if (item == null) if (item == null)

@ -7,6 +7,7 @@ import com.ruoyi.common.utils.DateUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.ruoyi.system.mapper.BizOrderTireDetailMapper; import com.ruoyi.system.mapper.BizOrderTireDetailMapper;
import com.ruoyi.system.domain.BizMaintenanceOrder;
import com.ruoyi.system.domain.BizOrderTireDetail; import com.ruoyi.system.domain.BizOrderTireDetail;
import com.ruoyi.system.service.IBizOrderTireDetailService; import com.ruoyi.system.service.IBizOrderTireDetailService;
import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.core.text.Convert;
@ -102,8 +103,32 @@ public class BizOrderTireDetailServiceImpl implements IBizOrderTireDetailService
return bizOrderTireDetailMapper.selectBizOrderTireDetail(bizOrderTireDetail); return bizOrderTireDetailMapper.selectBizOrderTireDetail(bizOrderTireDetail);
} }
@Override
public List<Map> selectBeforeSnapshot(Long orderId) {
return bizOrderTireDetailMapper.selectBeforeSnapshot(orderId);
}
@Override
public List<Map> selectAfterSnapshot(Long orderId) {
return bizOrderTireDetailMapper.selectAfterSnapshot(orderId);
}
@Override @Override
public List<Map> selectBaseTrieInstall(String plateNumber) { public List<Map> selectBaseTrieInstall(String plateNumber) {
return bizOrderTireDetailMapper.selectBaseTrieInstall(plateNumber); return bizOrderTireDetailMapper.selectBaseTrieInstall(plateNumber);
} }
@Override
public int countRealtimeHandledTyreRecords(BizMaintenanceOrder bizMaintenanceOrder) {
return bizOrderTireDetailMapper.countRealtimeHandledTyreRecords(bizMaintenanceOrder);
}
/**
*
* Service PDA
*/
@Override
public List<BizOrderTireDetail> selectScrapList(BizOrderTireDetail bizOrderTireDetail) {
return bizOrderTireDetailMapper.selectScrapList(bizOrderTireDetail);
}
} }

@ -4,6 +4,32 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.BaseCarLifecycleMapper"> <mapper namespace="com.ruoyi.system.mapper.BaseCarLifecycleMapper">
<sql id="computedLifecycleLastMileage">
COALESCE(
o.last_mileage,
(
SELECT MAX(rtm.mileage_old - rtm.mileage)
FROM record_tyre_mileage rtm
WHERE rtm.plate_number = o.plate_number
AND rtm.record_type = '保养'
AND o.create_time IS NOT NULL
AND rtm.create_time &gt;= o.create_time
<!-- 主单 update_time 可能早于明细/里程写入,所以上界用同车下一张维保工单创建时间。 -->
AND NOT EXISTS (
SELECT 1
FROM biz_maintenance_order next_order
WHERE next_order.plate_number = o.plate_number
AND next_order.type_code IN ('1', '2', '4', '5')
AND (
next_order.create_time &gt; o.create_time
OR (next_order.create_time = o.create_time AND next_order.order_id &gt; o.order_id)
)
AND next_order.create_time &lt;= rtm.create_time
)
)
)
</sql>
<resultMap type="CarLifecycleSummaryDTO" id="CarLifecycleSummaryResult"> <resultMap type="CarLifecycleSummaryDTO" id="CarLifecycleSummaryResult">
<result property="carId" column="car_id"/> <result property="carId" column="car_id"/>
<result property="carNo" column="car_no"/> <result property="carNo" column="car_no"/>
@ -47,8 +73,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
select tyre_rfid, pattern_depth, select tyre_rfid, pattern_depth,
row_number() over (partition by tyre_rfid order by create_time desc, id desc) as rn row_number() over (partition by tyre_rfid order by create_time desc, id desc) as rn
from record_tyre_mileage from record_tyre_mileage
where record_type = '保养' where pattern_depth is not null
and pattern_depth is not null
and pattern_depth &lt;&gt; '' and pattern_depth &lt;&gt; ''
) ranked ) ranked
where rn = 1 where rn = 1
@ -83,7 +108,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
o.type_code as typeCode, o.type_code as typeCode,
o.status as status, o.status as status,
o.input_mileage as inputMileage, o.input_mileage as inputMileage,
o.last_mileage as lastMileage, <include refid="computedLifecycleLastMileage"/> as lastMileage,
o.maintain_date as maintainDate, o.maintain_date as maintainDate,
d.dept_name as factoryName, d.dept_name as factoryName,
o.description as description o.description as description

@ -32,6 +32,32 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
select order_id, order_no, vehicle_id, plate_number, type_code, factory_id, input_mileage, last_mileage, maintain_date, description, status, order_type, create_by, create_time, update_by, update_time, remark from biz_maintenance_order select order_id, order_no, vehicle_id, plate_number, type_code, factory_id, input_mileage, last_mileage, maintain_date, description, status, order_type, create_by, create_time, update_by, update_time, remark from biz_maintenance_order
</sql> </sql>
<sql id="computedLastMileage">
COALESCE(
bmo.last_mileage,
(
SELECT MAX(rtm.mileage_old - rtm.mileage)
FROM record_tyre_mileage rtm
WHERE rtm.plate_number = bmo.plate_number
AND rtm.record_type = '保养'
AND bmo.create_time IS NOT NULL
AND rtm.create_time &gt;= bmo.create_time
<!-- 主单 update_time 可能早于明细/里程写入,所以上界用同车下一张维保工单创建时间。 -->
AND NOT EXISTS (
SELECT 1
FROM biz_maintenance_order next_order
WHERE next_order.plate_number = bmo.plate_number
AND next_order.type_code IN ('1', '2', '4', '5')
AND (
next_order.create_time &gt; bmo.create_time
OR (next_order.create_time = bmo.create_time AND next_order.order_id &gt; bmo.order_id)
)
AND next_order.create_time &lt;= rtm.create_time
)
)
)
</sql>
<select id="selectBizMaintenanceOrderList" parameterType="BizMaintenanceOrder" resultMap="BizMaintenanceOrderResult"> <select id="selectBizMaintenanceOrderList" parameterType="BizMaintenanceOrder" resultMap="BizMaintenanceOrderResult">
<include refid="selectBizMaintenanceOrderVo"/> <include refid="selectBizMaintenanceOrderVo"/>
<where> <where>
@ -56,7 +82,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select> </select>
<select id="selectBizMaintenanceOrderListTwo" parameterType="BizMaintenanceOrder" resultMap="BizMaintenanceOrderResult"> <select id="selectBizMaintenanceOrderListTwo" parameterType="BizMaintenanceOrder" resultMap="BizMaintenanceOrderResult">
select order_id, order_no, vehicle_id, plate_number, type_code, factory_id, d.dept_name as factoryName, input_mileage, last_mileage, maintain_date, description, bmo.status, order_type, select order_id, order_no, vehicle_id, plate_number, type_code, factory_id, d.dept_name as factoryName, input_mileage,
<include refid="computedLastMileage"/> as last_mileage, maintain_date, description, bmo.status, order_type,
bmo.create_by,su.user_name, bmo.create_time, bmo.update_by,sus.user_name as update_name, bmo.update_time, bmo.remark bmo.create_by,su.user_name, bmo.create_time, bmo.update_by,sus.user_name as update_name, bmo.update_time, bmo.remark
from biz_maintenance_order bmo from biz_maintenance_order bmo
left join sys_user su ON su.login_name = bmo.create_by left join sys_user su ON su.login_name = bmo.create_by
@ -66,7 +93,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="orderNo != null and orderNo != ''"> and order_no = #{orderNo}</if> <if test="orderNo != null and orderNo != ''"> and order_no = #{orderNo}</if>
<if test="vehicleId != null "> and vehicle_id = #{vehicleId}</if> <if test="vehicleId != null "> and vehicle_id = #{vehicleId}</if>
<if test="plateNumber != null and plateNumber != ''"> and plate_number like concat('%', #{plateNumber}, '%')</if> <if test="plateNumber != null and plateNumber != ''"> and plate_number like concat('%', #{plateNumber}, '%')</if>
<if test="typeCode == null or typeCode == ''"> and type_code in ('1','4')</if> <if test="typeCode == null or typeCode == ''"> and type_code in ('1','2','4','5')</if>
<if test="typeCode != null and typeCode != ''"> and type_code = #{typeCode}</if> <if test="typeCode != null and typeCode != ''"> and type_code = #{typeCode}</if>
<if test="factoryId != null "> and factory_id = #{factoryId}</if> <if test="factoryId != null "> and factory_id = #{factoryId}</if>
<if test="inputMileage != null "> and input_mileage = #{inputMileage}</if> <if test="inputMileage != null "> and input_mileage = #{inputMileage}</if>
@ -76,10 +103,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="status != null and status != ''"> and bmo.status = #{status}</if> <if test="status != null and status != ''"> and bmo.status = #{status}</if>
<if test="orderType != null and orderType != ''"> and order_type = #{orderType}</if> <if test="orderType != null and orderType != ''"> and order_type = #{orderType}</if>
<if test="params.beginTime != null and params.beginTime != ''"> <if test="params.beginTime != null and params.beginTime != ''">
and date_format(maintain_date,'%Y%m%d') &gt;= date_format(#{params.beginTime},'%Y%m%d') and maintain_date &gt;= #{params.beginTime}
</if> </if>
<if test="params.endTime != null and params.endTime != ''"> <if test="params.endTime != null and params.endTime != ''">
and date_format(maintain_date,'%Y%m%d') &lt;= date_format(#{params.endTime},'%Y%m%d') and maintain_date &lt;= #{params.endTime}
</if> </if>
${params.dataScope} ${params.dataScope}
</where> </where>
@ -94,10 +121,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
type_code, type_code,
sd.dept_name as factoryName, sd.dept_name as factoryName,
input_mileage, input_mileage,
last_mileage, <include refid="computedLastMileage"/> as last_mileage,
maintain_date, maintain_date,
description, description,
bmo.`STATUS`, bmo.status as status,
order_type, order_type,
su.user_name as create_by, su.user_name as create_by,
bmo.create_time, bmo.create_time,
@ -187,7 +214,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
bmo.order_id != #{orderId} bmo.order_id != #{orderId}
AND bmo.plate_number = #{plateNumber} AND bmo.plate_number = #{plateNumber}
AND bmo.status = 'COMPLETED' AND bmo.status = 'COMPLETED'
AND bmo.type_code IN ('1', '4') AND bmo.type_code IN ('1', '2', '4', '5')
<!-- 维保前按“上一张已完成维保工单的维保后结果”取数,必须排除当前工单之后创建的完成单。 --> <!-- 维保前按“上一张已完成维保工单的维保后结果”取数,必须排除当前工单之后创建的完成单。 -->
<if test="createTime != null"> <if test="createTime != null">
AND (bmo.create_time &lt; #{createTime} AND (bmo.create_time &lt; #{createTime}

@ -19,6 +19,20 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="updateTime" column="update_time" /> <result property="updateTime" column="update_time" />
<result property="remark" column="remark" /> <result property="remark" column="remark" />
<result property="positionName" column="position_name" /> <result property="positionName" column="position_name" />
<result property="scrapTime" column="scrap_time" />
<result property="orderNo" column="order_no" />
<result property="plateNumber" column="plate_number" />
<result property="typeCode" column="type_code" />
<result property="orderStatus" column="order_status" />
<result property="factoryName" column="factory_name" />
<result property="inputMileage" column="input_mileage" />
<result property="maintainDate" column="maintain_date" />
<result property="tyreNo" column="tyre_no" />
<result property="tyreEpc" column="tyre_epc" />
<result property="selfNo" column="self_no" />
<result property="tyreBrand" column="tyre_brand" />
<result property="tyreModel" column="tyre_model" />
<result property="photoCount" column="photo_count" />
</resultMap> </resultMap>
<sql id="selectBizOrderTireDetailVo"> <sql id="selectBizOrderTireDetailVo">
@ -55,7 +69,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
botd.tire_code as dot, botd.tire_code as dot,
botd.tread_depth as depth, botd.tread_depth as depth,
sdd.dict_label as position, sdd.dict_label as position,
IFNULL(botd.tire_status, 'normal') as status COALESCE(botd.tire_status, 'normal') as status
FROM FROM
biz_order_tire_detail botd biz_order_tire_detail botd
LEFT JOIN base_tyre bt ON bt.tyre_id = botd.tire_id LEFT JOIN base_tyre bt ON bt.tyre_id = botd.tire_id
@ -64,6 +78,128 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="orderId != null "> and order_id = #{orderId}</if> <if test="orderId != null "> and order_id = #{orderId}</if>
</where> </where>
</select> </select>
<!--
维保前快照:工单明细只保存本次完成后的胎纹,维保前胎纹需回看同一轮胎在本次明细写入前的最近里程快照。
这样 15mm -> 14mm 的保养过程不会在前后两侧都显示成 14mm。
-->
<select id="selectBeforeSnapshot" resultType="java.util.Map" parameterType="long">
SELECT
bt.tyre_brand AS brand,
bt.tyre_model AS spec,
botd.tire_code AS dot,
COALESCE(prev_mileage.pattern_depth, botd.tread_depth) AS depth,
sdd.dict_label AS position,
CASE
WHEN botd.tire_status = '换新胎,卸车' THEN 'removed'
ELSE 'normal'
END AS status
FROM biz_order_tire_detail botd
LEFT JOIN base_tyre bt ON bt.tyre_id = botd.tire_id
LEFT JOIN sys_dict_data sdd ON sdd.dict_code = botd.position_id
AND sdd.dict_type = 'WheelPosition'
LEFT JOIN record_tyre_mileage prev_mileage
ON prev_mileage.tyre_rfid = bt.tyre_epc
AND prev_mileage.pattern_depth IS NOT NULL
AND prev_mileage.pattern_depth &lt;&gt; ''
AND botd.create_time IS NOT NULL
AND prev_mileage.create_time &lt; botd.create_time
AND NOT EXISTS (
SELECT 1
FROM record_tyre_mileage newer_mileage
WHERE newer_mileage.tyre_rfid = prev_mileage.tyre_rfid
AND newer_mileage.pattern_depth IS NOT NULL
AND newer_mileage.pattern_depth &lt;&gt; ''
AND newer_mileage.create_time &lt; botd.create_time
AND (
newer_mileage.create_time &gt; prev_mileage.create_time
OR (newer_mileage.create_time = prev_mileage.create_time AND newer_mileage.id &gt; prev_mileage.id)
)
)
WHERE botd.order_id = #{orderId}
AND botd.tire_status IN ('换新胎,卸车', '保养')
</select>
<!--
维保后快照:除最终提交的工单明细外,还补上工单时间窗内 PDA 已实时写入的装胎记录。
PDA 装胎会先改 base_tyre / record_tyre_install再回到工单完成页不能只依赖 biz_order_tire_detail。
-->
<select id="selectAfterSnapshot" resultType="java.util.Map" parameterType="long">
SELECT
bt.tyre_brand AS brand,
bt.tyre_model AS spec,
botd.tire_code AS dot,
botd.tread_depth AS depth,
sdd.dict_label AS position,
CASE
WHEN botd.tire_status = '换新胎,装车' THEN 'installed'
ELSE 'normal'
END AS status
FROM biz_order_tire_detail botd
LEFT JOIN base_tyre bt ON bt.tyre_id = botd.tire_id
LEFT JOIN sys_dict_data sdd ON sdd.dict_code = botd.position_id
AND sdd.dict_type = 'WheelPosition'
WHERE botd.order_id = #{orderId}
AND botd.tire_status IN ('换新胎,装车', '保养')
UNION ALL
SELECT
bt.tyre_brand AS brand,
bt.tyre_model AS spec,
COALESCE(bt.tyre_no, bt.self_no, rti.tyre_rfid) AS dot,
COALESCE(rti.pattern_depth, bt.pattern_depth) AS depth,
rti.wheel_postion AS position,
'installed' AS status
FROM biz_maintenance_order bmo
INNER JOIN record_tyre_install rti ON rti.car_no = bmo.plate_number
LEFT JOIN base_tyre bt ON bt.tyre_epc = rti.tyre_rfid
WHERE bmo.order_id = #{orderId}
AND rti.type = '0'
AND bmo.create_time IS NOT NULL
AND rti.create_time &gt;= bmo.create_time
<!-- 装胎会在主单完成后继续写明细,不能用 update_time 截断,只能用下一张同车维保工单分隔窗口。 -->
AND NOT EXISTS (
SELECT 1
FROM biz_maintenance_order next_order
WHERE next_order.plate_number = bmo.plate_number
AND next_order.type_code IN ('1', '2', '4', '5')
AND (
next_order.create_time &gt; bmo.create_time
OR (next_order.create_time = bmo.create_time AND next_order.order_id &gt; bmo.order_id)
)
AND next_order.create_time &lt;= rti.create_time
)
AND NOT EXISTS (
SELECT 1
FROM biz_order_tire_detail existing_detail
LEFT JOIN base_tyre existing_tyre ON existing_tyre.tyre_id = existing_detail.tire_id
WHERE existing_detail.order_id = bmo.order_id
AND existing_tyre.tyre_epc = rti.tyre_rfid
)
AND NOT EXISTS (
SELECT 1
FROM record_tyre_install newer_install
WHERE newer_install.tyre_rfid = rti.tyre_rfid
AND newer_install.car_no = rti.car_no
AND newer_install.type = rti.type
AND newer_install.create_time &gt;= bmo.create_time
AND NOT EXISTS (
SELECT 1
FROM biz_maintenance_order next_order
WHERE next_order.plate_number = bmo.plate_number
AND next_order.type_code IN ('1', '2', '4', '5')
AND (
next_order.create_time &gt; bmo.create_time
OR (next_order.create_time = bmo.create_time AND next_order.order_id &gt; bmo.order_id)
)
AND next_order.create_time &lt;= newer_install.create_time
)
AND (
newer_install.create_time &gt; rti.create_time
OR (newer_install.create_time = rti.create_time AND newer_install.id &gt; rti.id)
)
)
</select>
<select id="selectBaseTrieInstall" resultType="java.util.Map" parameterType="String"> <select id="selectBaseTrieInstall" resultType="java.util.Map" parameterType="String">
SELECT SELECT
bt.tyre_brand as brand, bt.tyre_brand as brand,
@ -78,6 +214,82 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
car_no = #{plateNumber} car_no = #{plateNumber}
</select> </select>
<select id="selectScrapList" parameterType="BizOrderTireDetail" resultMap="BizOrderTireDetailResult">
<bind name="orderNoLike" value="orderNo == null ? null : '%' + orderNo + '%'" />
<bind name="plateNumberLike" value="plateNumber == null ? null : '%' + plateNumber + '%'" />
<bind name="tireCodeLike" value="tireCode == null ? null : '%' + tireCode + '%'" />
<bind name="tyreNoLike" value="tyreNo == null ? null : '%' + tyreNo + '%'" />
<bind name="factoryNameLike" value="factoryName == null ? null : '%' + factoryName + '%'" />
SELECT
botd.detail_id,
botd.order_id,
botd.position_id,
botd.tire_id,
botd.tire_code,
botd.tread_depth,
botd.tire_press,
botd.tire_status,
su.user_name AS create_by,
botd.create_time,
botd.update_by,
botd.update_time,
botd.remark,
sdd.dict_label AS position_name,
COALESCE(bmo.maintain_date, botd.create_time) AS scrap_time,
bmo.order_no,
bmo.plate_number,
bmo.type_code,
bmo.status AS order_status,
sd.dept_name AS factory_name,
bmo.input_mileage,
bmo.maintain_date,
bt.tyre_no,
bt.tyre_epc,
bt.self_no,
bt.tyre_brand,
bt.tyre_model,
(
SELECT COUNT(1)
FROM sys_attachment sa
WHERE sa.order_id = botd.order_id
) AS photo_count
FROM biz_order_tire_detail botd
LEFT JOIN biz_maintenance_order bmo ON bmo.order_id = botd.order_id
LEFT JOIN base_tyre bt ON bt.tyre_id = botd.tire_id
LEFT JOIN sys_dept sd ON sd.dept_id = bmo.factory_id
LEFT JOIN sys_user su ON su.login_name = botd.create_by
LEFT JOIN sys_dict_data sdd ON sdd.dict_code = botd.position_id AND sdd.dict_type = 'WheelPosition'
<where>
botd.tire_status = '报废'
<if test="detailId != null"> AND botd.detail_id = #{detailId}</if>
<if test="orderId != null"> AND botd.order_id = #{orderId}</if>
<if test="tireId != null"> AND botd.tire_id = #{tireId}</if>
<if test="orderNo != null and orderNo != ''"> AND bmo.order_no LIKE #{orderNoLike}</if>
<if test="plateNumber != null and plateNumber != ''"> AND bmo.plate_number LIKE #{plateNumberLike}</if>
<if test="tireCode != null and tireCode != ''"> AND botd.tire_code LIKE #{tireCodeLike}</if>
<if test="tyreNo != null and tyreNo != ''"> AND bt.tyre_no LIKE #{tyreNoLike}</if>
<if test="typeCode != null and typeCode != ''"> AND bmo.type_code = #{typeCode}</if>
<if test="orderStatus != null and orderStatus != ''"> AND bmo.status = #{orderStatus}</if>
<if test="factoryName != null and factoryName != ''"> AND sd.dept_name LIKE #{factoryNameLike}</if>
<if test="params.beginTimeDate != null"> AND COALESCE(bmo.maintain_date, botd.create_time) &gt;= #{params.beginTimeDate}</if>
<if test="params.endTimeExclusive != null"> AND COALESCE(bmo.maintain_date, botd.create_time) &lt; #{params.endTimeExclusive}</if>
</where>
ORDER BY scrap_time DESC, botd.detail_id DESC
</select>
<select id="countRealtimeHandledTyreRecords" parameterType="BizMaintenanceOrder" resultType="int">
SELECT COUNT(1)
FROM record_tyre_install rti
WHERE rti.car_no = #{plateNumber}
AND rti.type IN ('0', '1')
<if test="createTime != null">
AND rti.create_time &gt;= #{createTime}
</if>
<if test="updateTime != null">
AND rti.create_time &lt;= #{updateTime}
</if>
</select>
<insert id="insertBizOrderTireDetail" parameterType="BizOrderTireDetail" useGeneratedKeys="true" keyProperty="detailId"> <insert id="insertBizOrderTireDetail" parameterType="BizOrderTireDetail" useGeneratedKeys="true" keyProperty="detailId">
insert into biz_order_tire_detail insert into biz_order_tire_detail
<trim prefix="(" suffix=")" suffixOverrides=","> <trim prefix="(" suffix=")" suffixOverrides=",">

Loading…
Cancel
Save