feat(asset): 添加资产报修维修功能模块

- 新增报修单创建页面,支持资产选择和故障描述录入
- 添加报修操作上下文类,统一管理操作人信息
- 创建报修订单实体类,定义完整的报修流程字段结构
- 实现报修订单控制器,提供从新增到完成的完整业务流程
- 添加报修订单数据访问层,支持查询和状态变更操作
- 集成报表单模板,实现前端表单验证和交互逻辑
main
yangk 1 week ago
parent f50c0f9aa3
commit 5f15c3f1f9

3
.gitignore vendored

@ -45,3 +45,6 @@ doc/
!*/build/*.java
!*/build/*.html
!*/build/*.xml
# Exclude sql directory
sql/

@ -0,0 +1,279 @@
package com.ruoyi.asset.controller;
import java.util.List;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
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.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.asset.domain.AmsAsset;
import com.ruoyi.asset.domain.AmsRepairOperateContext;
import com.ruoyi.asset.domain.AmsRepairOrder;
import com.ruoyi.asset.service.IAmsRepairOrderService;
import com.ruoyi.system.service.ISysUserService;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.core.page.TableDataInfo;
/**
* Controller
*
* 稿API
*
* @author Yangk
* @date 2026-06-16
*/
@Controller
@RequestMapping("/asset/repair")
public class AmsRepairOrderController extends BaseController
{
private String prefix = "asset/repair";
@Autowired
private IAmsRepairOrderService amsRepairOrderService;
@Autowired
private ISysUserService sysUserService;
@RequiresPermissions("asset:repair:view")
@GetMapping()
public String repair()
{
return prefix + "/repair";
}
/**
*
*/
@RequiresPermissions("asset:repair:list")
@PostMapping("/list")
@ResponseBody
public TableDataInfo list(AmsRepairOrder amsRepairOrder)
{
startPage();
List<AmsRepairOrder> list = amsRepairOrderService.selectAmsRepairOrderList(amsRepairOrder);
return getDataTable(list);
}
/**
*
*/
@RequiresPermissions(value = { "asset:repair:add", "asset:repair:edit" }, logical = Logical.OR)
@GetMapping("/selectAsset")
public String selectAsset(@RequestParam(value = "repairId", required = false) Long repairId, ModelMap mmap)
{
mmap.put("repairId", repairId);
return prefix + "/selectAsset";
}
/**
* /
*/
@RequiresPermissions(value = { "asset:repair:add", "asset:repair:edit" }, logical = Logical.OR)
@PostMapping("/availableAssetList")
@ResponseBody
public TableDataInfo availableAssetList(AmsAsset asset, @RequestParam(value = "repairId", required = false) Long repairId)
{
startPage();
List<AmsAsset> list = amsRepairOrderService.selectAvailableRepairAssetList(asset, repairId);
return getDataTable(list);
}
/**
*
*/
@RequiresPermissions("asset:repair:export")
@Log(title = "报修维修管理", businessType = BusinessType.EXPORT)
@PostMapping("/export")
@ResponseBody
public AjaxResult export(AmsRepairOrder amsRepairOrder)
{
List<AmsRepairOrder> list = amsRepairOrderService.selectAmsRepairOrderList(amsRepairOrder);
ExcelUtil<AmsRepairOrder> util = new ExcelUtil<AmsRepairOrder>(AmsRepairOrder.class);
return util.exportExcel(list, "报修维修管理数据");
}
/**
*
*/
@RequiresPermissions("asset:repair:view")
@GetMapping("/view/{repairId}")
public String view(@PathVariable("repairId") Long repairId, ModelMap mmap)
{
AmsRepairOrder amsRepairOrder = amsRepairOrderService.selectAmsRepairOrderByRepairId(repairId);
mmap.put("amsRepairOrder", amsRepairOrder);
return prefix + "/view";
}
/**
*
*/
@RequiresPermissions("asset:repair:add")
@GetMapping("/add")
public String add(ModelMap mmap)
{
putUserOptions(mmap);
return prefix + "/add";
}
/**
* 稿
*/
@RequiresPermissions("asset:repair:add")
@Log(title = "报修维修管理", businessType = BusinessType.INSERT)
@PostMapping("/add")
@ResponseBody
public AjaxResult addSave(AmsRepairOrder amsRepairOrder)
{
return toAjax(amsRepairOrderService.insertAmsRepairOrder(amsRepairOrder, buildOperateContext()));
}
/**
*
*/
@RequiresPermissions("asset:repair:edit")
@GetMapping("/edit/{repairId}")
public String edit(@PathVariable("repairId") Long repairId, ModelMap mmap)
{
AmsRepairOrder amsRepairOrder = amsRepairOrderService.selectAmsRepairOrderByRepairId(repairId);
mmap.put("amsRepairOrder", amsRepairOrder);
putUserOptions(mmap);
return prefix + "/edit";
}
/**
* 稿
*/
@RequiresPermissions("asset:repair:edit")
@Log(title = "报修维修管理", businessType = BusinessType.UPDATE)
@PostMapping("/edit")
@ResponseBody
public AjaxResult editSave(AmsRepairOrder amsRepairOrder)
{
return toAjax(amsRepairOrderService.updateAmsRepairOrder(amsRepairOrder, buildOperateContext()));
}
/**
* 稿
*/
@RequiresPermissions("asset:repair:remove")
@Log(title = "报修维修管理", businessType = BusinessType.DELETE)
@PostMapping("/remove")
@ResponseBody
public AjaxResult remove(String ids)
{
return toAjax(amsRepairOrderService.deleteAmsRepairOrderByRepairIds(ids));
}
/**
*
*/
@RequiresPermissions("asset:repair:submit")
@Log(title = "报修维修管理", businessType = BusinessType.UPDATE)
@PostMapping("/submit/{repairId}")
@ResponseBody
public AjaxResult submit(@PathVariable("repairId") Long repairId)
{
return toAjax(amsRepairOrderService.submitRepair(repairId, buildOperateContext()));
}
/**
*
*/
@RequiresPermissions("asset:repair:confirm")
@Log(title = "报修维修管理", businessType = BusinessType.UPDATE)
@PostMapping("/confirm/{repairId}")
@ResponseBody
public AjaxResult confirm(@PathVariable("repairId") Long repairId)
{
return toAjax(amsRepairOrderService.confirmRepair(repairId, buildOperateContext()));
}
/**
*
*/
@RequiresPermissions("asset:repair:reject")
@Log(title = "报修维修管理", businessType = BusinessType.UPDATE)
@PostMapping("/reject/{repairId}")
@ResponseBody
public AjaxResult reject(@PathVariable("repairId") Long repairId, @RequestParam("rejectReason") String rejectReason)
{
return toAjax(amsRepairOrderService.rejectRepair(repairId, rejectReason, buildOperateContext()));
}
/**
*
*/
@RequiresPermissions("asset:repair:start")
@GetMapping("/start/{repairId}")
public String start(@PathVariable("repairId") Long repairId, ModelMap mmap)
{
AmsRepairOrder amsRepairOrder = amsRepairOrderService.selectAmsRepairOrderByRepairId(repairId);
mmap.put("amsRepairOrder", amsRepairOrder);
putUserOptions(mmap);
return prefix + "/start";
}
/**
*
*/
@RequiresPermissions("asset:repair:start")
@Log(title = "报修维修管理", businessType = BusinessType.UPDATE)
@PostMapping("/start")
@ResponseBody
public AjaxResult startSave(AmsRepairOrder amsRepairOrder)
{
return toAjax(amsRepairOrderService.startRepair(amsRepairOrder, buildOperateContext()));
}
/**
*
*/
@RequiresPermissions("asset:repair:finish")
@GetMapping("/finish/{repairId}")
public String finish(@PathVariable("repairId") Long repairId, ModelMap mmap)
{
AmsRepairOrder amsRepairOrder = amsRepairOrderService.selectAmsRepairOrderByRepairId(repairId);
mmap.put("amsRepairOrder", amsRepairOrder);
return prefix + "/finish";
}
/**
*
*/
@RequiresPermissions("asset:repair:finish")
@Log(title = "报修维修管理", businessType = BusinessType.UPDATE)
@PostMapping("/finish")
@ResponseBody
public AjaxResult finishSave(AmsRepairOrder amsRepairOrder)
{
return toAjax(amsRepairOrderService.finishRepair(amsRepairOrder, buildOperateContext()));
}
private AmsRepairOperateContext buildOperateContext()
{
SysUser currentUser = getSysUser();
return new AmsRepairOperateContext(currentUser.getUserId(), currentUser.getUserName(), getLoginName());
}
private void putUserOptions(ModelMap mmap)
{
SysUser user = new SysUser();
user.setStatus(UserConstants.NORMAL);
mmap.put("userList", sysUserService.selectUserList(user));
SysUser currentUser = getSysUser();
mmap.put("defaultReportUserId", currentUser.getUserId());
mmap.put("defaultReportUserName", currentUser.getUserName());
}
}

@ -0,0 +1,65 @@
package com.ruoyi.asset.domain;
import java.io.Serializable;
/**
*
*
* Controller Service
*
* @author Yangk
*/
public class AmsRepairOperateContext implements Serializable
{
private static final long serialVersionUID = 1L;
/** 操作人ID */
private Long operateUserId;
/** 操作人名称 */
private String operateUserName;
/** 操作人登录账号 */
private String operateLoginName;
public AmsRepairOperateContext()
{
}
public AmsRepairOperateContext(Long operateUserId, String operateUserName, String operateLoginName)
{
this.operateUserId = operateUserId;
this.operateUserName = operateUserName;
this.operateLoginName = operateLoginName;
}
public Long getOperateUserId()
{
return operateUserId;
}
public void setOperateUserId(Long operateUserId)
{
this.operateUserId = operateUserId;
}
public String getOperateUserName()
{
return operateUserName;
}
public void setOperateUserName(String operateUserName)
{
this.operateUserName = operateUserName;
}
public String getOperateLoginName()
{
return operateLoginName;
}
public void setOperateLoginName(String operateLoginName)
{
this.operateLoginName = operateLoginName;
}
}

@ -0,0 +1,453 @@
package com.ruoyi.asset.domain;
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.ToStringStyle;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
/**
* ams_repair_order
*
* @author Yangk
* @date 2026-06-16
*/
public class AmsRepairOrder extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 维修单ID */
private Long repairId;
/** 报修单号 */
@Excel(name = "报修单号")
private String repairNo;
/** 资产ID */
@Excel(name = "资产ID")
private Long assetId;
/** 资产编码快照 */
@Excel(name = "资产编码快照")
private String assetCode;
/** 资产名称快照 */
@Excel(name = "资产名称快照")
private String assetName;
/** 资产类别ID快照 */
@Excel(name = "资产类别ID快照")
private Long categoryId;
/** 类别编码快照 */
@Excel(name = "类别编码快照")
private String categoryCode;
/** 类别名称快照 */
@Excel(name = "类别名称快照")
private String categoryName;
/** 规格型号快照 */
@Excel(name = "规格型号快照")
private String specModel;
/** 品牌快照 */
@Excel(name = "品牌快照")
private String brand;
/** 报修人ID */
@Excel(name = "报修人ID")
private Long reportUserId;
/** 报修人名称快照 */
@Excel(name = "报修人名称快照")
private String reportUserName;
/** 故障描述 */
@Excel(name = "故障描述")
private String faultDesc;
/** 报修时间 */
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
@Excel(name = "报修时间", width = 30, dateFormat = "yyyy-MM-dd")
private Date reportTime;
/** 预计完成时间 */
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
@Excel(name = "预计完成时间", width = 30, dateFormat = "yyyy-MM-dd")
private Date expectedFinishTime;
/** 维修方类型 */
@Excel(name = "维修方类型")
private String repairerType;
/** 维修人ID选择系统用户时保存 */
@Excel(name = "维修人ID选择系统用户时保存")
private Long repairUserId;
/** 维修人名称快照或手工填写姓名 */
@Excel(name = "维修人名称快照或手工填写姓名")
private String repairUserName;
/** 外部维修单位名称 */
@Excel(name = "外部维修单位名称")
private String repairOrgName;
/** 维修联系电话 */
@Excel(name = "维修联系电话")
private String repairContactPhone;
/** 开始维修时间 */
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
@Excel(name = "开始维修时间", width = 30, dateFormat = "yyyy-MM-dd")
private Date repairStartTime;
/** 维修完成时间 */
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
@Excel(name = "维修完成时间", width = 30, dateFormat = "yyyy-MM-dd")
private Date repairFinishTime;
/** 维修结果 */
@Excel(name = "维修结果")
private String repairResult;
/** 维修费用 */
@Excel(name = "维修费用")
private BigDecimal repairCost;
/** 维修前资产状态 */
@Excel(name = "维修前资产状态")
private String beforeAssetStatus;
/** 驳回原因 */
@Excel(name = "驳回原因")
private String rejectReason;
/** 单据状态 */
@Excel(name = "单据状态")
private String orderStatus;
/** 删除标志0存在1删除 */
private String delFlag;
public void setRepairId(Long repairId)
{
this.repairId = repairId;
}
public Long getRepairId()
{
return repairId;
}
public void setRepairNo(String repairNo)
{
this.repairNo = repairNo;
}
public String getRepairNo()
{
return repairNo;
}
public void setAssetId(Long assetId)
{
this.assetId = assetId;
}
public Long getAssetId()
{
return assetId;
}
public void setAssetCode(String assetCode)
{
this.assetCode = assetCode;
}
public String getAssetCode()
{
return assetCode;
}
public void setAssetName(String assetName)
{
this.assetName = assetName;
}
public String getAssetName()
{
return assetName;
}
public void setCategoryId(Long categoryId)
{
this.categoryId = categoryId;
}
public Long getCategoryId()
{
return categoryId;
}
public void setCategoryCode(String categoryCode)
{
this.categoryCode = categoryCode;
}
public String getCategoryCode()
{
return categoryCode;
}
public void setCategoryName(String categoryName)
{
this.categoryName = categoryName;
}
public String getCategoryName()
{
return categoryName;
}
public void setSpecModel(String specModel)
{
this.specModel = specModel;
}
public String getSpecModel()
{
return specModel;
}
public void setBrand(String brand)
{
this.brand = brand;
}
public String getBrand()
{
return brand;
}
public void setReportUserId(Long reportUserId)
{
this.reportUserId = reportUserId;
}
public Long getReportUserId()
{
return reportUserId;
}
public void setReportUserName(String reportUserName)
{
this.reportUserName = reportUserName;
}
public String getReportUserName()
{
return reportUserName;
}
public void setFaultDesc(String faultDesc)
{
this.faultDesc = faultDesc;
}
public String getFaultDesc()
{
return faultDesc;
}
public void setReportTime(Date reportTime)
{
this.reportTime = reportTime;
}
public Date getReportTime()
{
return reportTime;
}
public void setExpectedFinishTime(Date expectedFinishTime)
{
this.expectedFinishTime = expectedFinishTime;
}
public Date getExpectedFinishTime()
{
return expectedFinishTime;
}
public void setRepairerType(String repairerType)
{
this.repairerType = repairerType;
}
public String getRepairerType()
{
return repairerType;
}
public void setRepairUserId(Long repairUserId)
{
this.repairUserId = repairUserId;
}
public Long getRepairUserId()
{
return repairUserId;
}
public void setRepairUserName(String repairUserName)
{
this.repairUserName = repairUserName;
}
public String getRepairUserName()
{
return repairUserName;
}
public void setRepairOrgName(String repairOrgName)
{
this.repairOrgName = repairOrgName;
}
public String getRepairOrgName()
{
return repairOrgName;
}
public void setRepairContactPhone(String repairContactPhone)
{
this.repairContactPhone = repairContactPhone;
}
public String getRepairContactPhone()
{
return repairContactPhone;
}
public void setRepairStartTime(Date repairStartTime)
{
this.repairStartTime = repairStartTime;
}
public Date getRepairStartTime()
{
return repairStartTime;
}
public void setRepairFinishTime(Date repairFinishTime)
{
this.repairFinishTime = repairFinishTime;
}
public Date getRepairFinishTime()
{
return repairFinishTime;
}
public void setRepairResult(String repairResult)
{
this.repairResult = repairResult;
}
public String getRepairResult()
{
return repairResult;
}
public void setRepairCost(BigDecimal repairCost)
{
this.repairCost = repairCost;
}
public BigDecimal getRepairCost()
{
return repairCost;
}
public void setBeforeAssetStatus(String beforeAssetStatus)
{
this.beforeAssetStatus = beforeAssetStatus;
}
public String getBeforeAssetStatus()
{
return beforeAssetStatus;
}
public void setRejectReason(String rejectReason)
{
this.rejectReason = rejectReason;
}
public String getRejectReason()
{
return rejectReason;
}
public void setOrderStatus(String orderStatus)
{
this.orderStatus = orderStatus;
}
public String getOrderStatus()
{
return orderStatus;
}
public void setDelFlag(String delFlag)
{
this.delFlag = delFlag;
}
public String getDelFlag()
{
return delFlag;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("repairId", getRepairId())
.append("repairNo", getRepairNo())
.append("assetId", getAssetId())
.append("assetCode", getAssetCode())
.append("assetName", getAssetName())
.append("categoryId", getCategoryId())
.append("categoryCode", getCategoryCode())
.append("categoryName", getCategoryName())
.append("specModel", getSpecModel())
.append("brand", getBrand())
.append("reportUserId", getReportUserId())
.append("reportUserName", getReportUserName())
.append("faultDesc", getFaultDesc())
.append("reportTime", getReportTime())
.append("expectedFinishTime", getExpectedFinishTime())
.append("repairerType", getRepairerType())
.append("repairUserId", getRepairUserId())
.append("repairUserName", getRepairUserName())
.append("repairOrgName", getRepairOrgName())
.append("repairContactPhone", getRepairContactPhone())
.append("repairStartTime", getRepairStartTime())
.append("repairFinishTime", getRepairFinishTime())
.append("repairResult", getRepairResult())
.append("repairCost", getRepairCost())
.append("beforeAssetStatus", getBeforeAssetStatus())
.append("rejectReason", getRejectReason())
.append("orderStatus", getOrderStatus())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.append("remark", getRemark())
.append("delFlag", getDelFlag())
.toString();
}
}

@ -0,0 +1,97 @@
package com.ruoyi.asset.mapper;
import java.util.List;
import com.ruoyi.asset.domain.AmsAsset;
import com.ruoyi.asset.domain.AmsRepairOrder;
import org.apache.ibatis.annotations.Param;
/**
* Mapper
*
* @author Yangk
* @date 2026-06-16
*/
public interface AmsRepairOrderMapper
{
/**
*
*
* @param repairId
* @return
*/
public AmsRepairOrder selectAmsRepairOrderByRepairId(Long repairId);
/**
*
*
* @param repairId
* @return
*/
public AmsRepairOrder selectAmsRepairOrderByRepairIdForUpdate(Long repairId);
/**
*
*
* @param amsRepairOrder
* @return
*/
public List<AmsRepairOrder> selectAmsRepairOrderList(AmsRepairOrder amsRepairOrder);
/**
*
*
* @param amsRepairOrder
* @return
*/
public int insertAmsRepairOrder(AmsRepairOrder amsRepairOrder);
/**
*
*
* @param amsRepairOrder
* @return
*/
public int updateAmsRepairOrder(AmsRepairOrder amsRepairOrder);
/**
*
*
* @param repairId
* @return
*/
public int deleteAmsRepairOrderByRepairId(Long repairId);
/**
*
*
* @param repairIds
* @return
*/
public int deleteAmsRepairOrderByRepairIds(String[] repairIds);
/**
*
*
* @param assetId ID
* @param currentRepairId ID
* @param statuses
* @return
*/
public int countOtherActiveRepairOrderByAssetId(@Param("assetId") Long assetId,
@Param("currentRepairId") Long currentRepairId,
@Param("statuses") List<String> statuses);
/**
* /
*
* @param asset
* @param currentRepairId ID
* @param statuses IN_STOCK, IN_USE
* @param activeRepairStatuses
* @return
*/
public List<AmsAsset> selectAvailableRepairAssetList(@Param("asset") AmsAsset asset,
@Param("currentRepairId") Long currentRepairId,
@Param("statuses") List<String> statuses,
@Param("activeRepairStatuses") List<String> activeRepairStatuses);
}

@ -0,0 +1,119 @@
package com.ruoyi.asset.service;
import java.util.List;
import com.ruoyi.asset.domain.AmsRepairOperateContext;
import com.ruoyi.asset.domain.AmsRepairOrder;
/**
* Service
*
* @author Yangk
* @date 2026-06-16
*/
public interface IAmsRepairOrderService
{
/**
*
*
* @param repairId
* @return
*/
public AmsRepairOrder selectAmsRepairOrderByRepairId(Long repairId);
/**
*
*
* @param amsRepairOrder
* @return
*/
public List<AmsRepairOrder> selectAmsRepairOrderList(AmsRepairOrder amsRepairOrder);
/**
*
*
* @param amsRepairOrder
* @param operateContext
* @return
*/
public int insertAmsRepairOrder(AmsRepairOrder amsRepairOrder, AmsRepairOperateContext operateContext);
/**
*
*
* @param amsRepairOrder
* @param operateContext
* @return
*/
public int updateAmsRepairOrder(AmsRepairOrder amsRepairOrder, AmsRepairOperateContext operateContext);
/**
*
*
* @param repairIds
* @return
*/
public int deleteAmsRepairOrderByRepairIds(String repairIds);
/**
*
*
* @param repairId
* @return
*/
public int deleteAmsRepairOrderByRepairId(Long repairId);
/**
*
*
* @param repairId ID
* @param operateContext
* @return
*/
public int submitRepair(Long repairId, AmsRepairOperateContext operateContext);
/**
*
*
* @param repairId ID
* @param operateContext
* @return
*/
public int confirmRepair(Long repairId, AmsRepairOperateContext operateContext);
/**
*
*
* @param repairId ID
* @param rejectReason
* @param operateContext
* @return
*/
public int rejectRepair(Long repairId, String rejectReason, AmsRepairOperateContext operateContext);
/**
*
*
* @param amsRepairOrder
* @param operateContext
* @return
*/
public int startRepair(AmsRepairOrder amsRepairOrder, AmsRepairOperateContext operateContext);
/**
*
*
* @param amsRepairOrder
* @param operateContext
* @return
*/
public int finishRepair(AmsRepairOrder amsRepairOrder, AmsRepairOperateContext operateContext);
/**
* /
*
* @param asset
* @param currentRepairId ID
* @return
*/
public List<com.ruoyi.asset.domain.AmsAsset> selectAvailableRepairAssetList(com.ruoyi.asset.domain.AmsAsset asset, Long currentRepairId);
}

@ -0,0 +1,602 @@
package com.ruoyi.asset.service.impl;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.asset.domain.AmsAsset;
import com.ruoyi.asset.domain.AmsRepairOperateContext;
import com.ruoyi.asset.domain.AmsRepairOrder;
import com.ruoyi.asset.domain.AssetTransitionContext;
import com.ruoyi.asset.domain.AssetTransitionResult;
import com.ruoyi.asset.mapper.AmsAssetMapper;
import com.ruoyi.asset.mapper.AmsRepairOrderMapper;
import com.ruoyi.asset.service.IAmsRepairOrderService;
import com.ruoyi.asset.service.IAssetStatusTransitionService;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.service.ISysCodeRuleService;
/**
* Service
*
* /REPAIRING
* /
*
* @author Yangk
* @date 2026-06-16
*/
@Service
public class AmsRepairOrderServiceImpl implements IAmsRepairOrderService
{
private static final String REPAIR_ORDER_RULE = "REPAIR_ORDER";
private static final String DEL_FLAG_NORMAL = "0";
// 资产状态
private static final String ASSET_STATUS_IN_STOCK = "IN_STOCK";
private static final String ASSET_STATUS_IN_USE = "IN_USE";
// 报修单状态
private static final String STATUS_DRAFT = "DRAFT";
private static final String STATUS_PENDING_CONFIRM = "PENDING_CONFIRM";
private static final String STATUS_REJECTED = "REJECTED";
private static final String STATUS_WAIT_REPAIR = "WAIT_REPAIR";
private static final String STATUS_REPAIRING = "REPAIRING";
private static final String STATUS_REPAIR_DONE = "REPAIR_DONE";
// 维修方类型
private static final String REPAIRER_TYPE_INTERNAL = "INTERNAL";
private static final String REPAIRER_TYPE_EXTERNAL = "EXTERNAL";
@Autowired
private AmsRepairOrderMapper amsRepairOrderMapper;
@Autowired
private AmsAssetMapper amsAssetMapper;
@Autowired
private ISysCodeRuleService sysCodeRuleService;
@Autowired
private IAssetStatusTransitionService assetStatusTransitionService;
/**
*
*/
@Override
public AmsRepairOrder selectAmsRepairOrderByRepairId(Long repairId)
{
return amsRepairOrderMapper.selectAmsRepairOrderByRepairId(repairId);
}
/**
*
*/
@Override
public List<AmsRepairOrder> selectAmsRepairOrderList(AmsRepairOrder amsRepairOrder)
{
return amsRepairOrderMapper.selectAmsRepairOrderList(amsRepairOrder);
}
/**
* 稿
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int insertAmsRepairOrder(AmsRepairOrder amsRepairOrder, AmsRepairOperateContext operateContext)
{
validateOperateContext(operateContext);
// 1. 基础报文非空和长度校验
validateOrderRequest(amsRepairOrder);
// 2. 悲观锁锁定资产,校验原状态,并回填属性快照及前置状态
fillAssetSnapshots(amsRepairOrder, null);
// 3. 生成 BX 前缀的唯一报修单号
Date now = DateUtils.getNowDate();
amsRepairOrder.setRepairNo(sysCodeRuleService.nextCode(REPAIR_ORDER_RULE));
amsRepairOrder.setReportUserId(operateContext.getOperateUserId());
amsRepairOrder.setReportUserName(StringUtils.trim(operateContext.getOperateUserName()));
amsRepairOrder.setReportTime(now);
amsRepairOrder.setOrderStatus(STATUS_DRAFT);
amsRepairOrder.setDelFlag(DEL_FLAG_NORMAL);
amsRepairOrder.setCreateBy(operateContext.getOperateLoginName());
amsRepairOrder.setCreateTime(now);
// 重置受控的维修中及维修完成字段,以防篡改
amsRepairOrder.setRepairerType(REPAIRER_TYPE_INTERNAL);
amsRepairOrder.setRepairUserId(null);
amsRepairOrder.setRepairUserName(null);
amsRepairOrder.setRepairOrgName(null);
amsRepairOrder.setRepairContactPhone(null);
amsRepairOrder.setRepairStartTime(null);
amsRepairOrder.setRepairFinishTime(null);
amsRepairOrder.setRepairResult(null);
amsRepairOrder.setRepairCost(BigDecimal.ZERO);
amsRepairOrder.setBeforeAssetStatus(null);
amsRepairOrder.setRejectReason(null);
return amsRepairOrderMapper.insertAmsRepairOrder(amsRepairOrder);
}
/**
* 稿
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int updateAmsRepairOrder(AmsRepairOrder amsRepairOrder, AmsRepairOperateContext operateContext)
{
validateOperateContext(operateContext);
if (amsRepairOrder == null || amsRepairOrder.getRepairId() == null)
{
throw new ServiceException("单据ID不能为空");
}
// 1. 悲观锁锁定并校验状态必须为 DRAFT。非草稿状态严禁修改
AmsRepairOrder current = requireOrderForUpdate(amsRepairOrder.getRepairId(), STATUS_DRAFT,
"仅草稿状态的报修单允许修改");
validateOrderRequest(amsRepairOrder);
// 2. 锁定资产,校验原状态并重新回填快照
fillAssetSnapshots(amsRepairOrder, amsRepairOrder.getRepairId());
// 3. 强行还原单号等受控属性,清空受理及驳回信息,强制复归为 DRAFT
amsRepairOrder.setRepairNo(current.getRepairNo());
amsRepairOrder.setReportUserId(current.getReportUserId());
amsRepairOrder.setReportUserName(current.getReportUserName());
amsRepairOrder.setReportTime(current.getReportTime());
amsRepairOrder.setOrderStatus(STATUS_DRAFT);
amsRepairOrder.setCreateBy(current.getCreateBy());
amsRepairOrder.setCreateTime(current.getCreateTime());
amsRepairOrder.setDelFlag(current.getDelFlag());
amsRepairOrder.setUpdateBy(operateContext.getOperateLoginName());
amsRepairOrder.setUpdateTime(DateUtils.getNowDate());
// 重置受控字段
amsRepairOrder.setRepairerType(StringUtils.isEmpty(current.getRepairerType())
? REPAIRER_TYPE_INTERNAL : current.getRepairerType());
amsRepairOrder.setRepairUserId(null);
amsRepairOrder.setRepairUserName(null);
amsRepairOrder.setRepairOrgName(null);
amsRepairOrder.setRepairContactPhone(null);
amsRepairOrder.setRepairStartTime(null);
amsRepairOrder.setRepairFinishTime(null);
amsRepairOrder.setRepairResult(null);
amsRepairOrder.setRepairCost(BigDecimal.ZERO);
amsRepairOrder.setBeforeAssetStatus(null);
amsRepairOrder.setRejectReason(null);
return updateOrderOrThrow(amsRepairOrder, STATUS_DRAFT, "单据状态已发生改变,更新失败");
}
/**
* 稿
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int deleteAmsRepairOrderByRepairIds(String repairIds)
{
// 1. 将操作ID排序以彻底防范死锁
Long[] sortedIds = Arrays.stream(Convert.toStrArray(repairIds))
.map(Long::valueOf).sorted().toArray(Long[]::new);
int rows = 0;
for (Long repairId : sortedIds)
{
// 2. 获取排他锁,验证是否为 DRAFT 草稿单
AmsRepairOrder order = amsRepairOrderMapper.selectAmsRepairOrderByRepairIdForUpdate(repairId);
if (order == null)
{
continue;
}
if (!STATUS_DRAFT.equals(order.getOrderStatus()))
{
throw new ServiceException(StringUtils.format("报修单【{}】非草稿状态,不允许删除", order.getRepairNo()));
}
rows += amsRepairOrderMapper.deleteAmsRepairOrderByRepairId(repairId);
}
return rows;
}
/**
* 稿
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int deleteAmsRepairOrderByRepairId(Long repairId)
{
AmsRepairOrder order = amsRepairOrderMapper.selectAmsRepairOrderByRepairIdForUpdate(repairId);
if (order == null)
{
return 0;
}
if (!STATUS_DRAFT.equals(order.getOrderStatus()))
{
throw new ServiceException(StringUtils.format("报修单【{}】非草稿状态,不允许删除", order.getRepairNo()));
}
return amsRepairOrderMapper.deleteAmsRepairOrderByRepairId(repairId);
}
/**
*
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int submitRepair(Long repairId, AmsRepairOperateContext operateContext)
{
validateOperateContext(operateContext);
AmsRepairOrder order = requireOrderForUpdate(repairId, STATUS_DRAFT, "仅草稿状态的报修单允许提交");
// 二次校验实物状态与占用情况
validateAssetReadyForRepair(order.getAssetId(), order.getRepairId());
order.setOrderStatus(STATUS_PENDING_CONFIRM);
order.setUpdateBy(operateContext.getOperateLoginName());
order.setUpdateTime(DateUtils.getNowDate());
return updateOrderOrThrow(order, STATUS_DRAFT, "报修单提交失败,请刷新后重试");
}
/**
*
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int confirmRepair(Long repairId, AmsRepairOperateContext operateContext)
{
validateOperateContext(operateContext);
AmsRepairOrder order = requireOrderForUpdate(repairId, STATUS_PENDING_CONFIRM, "仅待确认状态的报修单允许受理");
// 二次校验实物状态与占用情况
validateAssetReadyForRepair(order.getAssetId(), order.getRepairId());
order.setOrderStatus(STATUS_WAIT_REPAIR);
order.setUpdateBy(operateContext.getOperateLoginName());
order.setUpdateTime(DateUtils.getNowDate());
order.setRejectReason(null); // 清除可能存在的驳回记录
return updateOrderOrThrow(order, STATUS_PENDING_CONFIRM, "报修单受理失败,请刷新后重试");
}
/**
*
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int rejectRepair(Long repairId, String rejectReason, AmsRepairOperateContext operateContext)
{
validateOperateContext(operateContext);
AmsRepairOrder order = requireOrderForUpdate(repairId, STATUS_PENDING_CONFIRM, "仅待确认状态的报修单允许驳回");
if (StringUtils.isEmpty(StringUtils.trim(rejectReason)))
{
throw new ServiceException("驳回原因不能为空");
}
validateLength(rejectReason, 500, "驳回原因");
order.setOrderStatus(STATUS_REJECTED);
order.setRejectReason(StringUtils.trim(rejectReason));
order.setUpdateBy(operateContext.getOperateLoginName());
order.setUpdateTime(DateUtils.getNowDate());
return updateOrderOrThrow(order, STATUS_PENDING_CONFIRM, "报修单驳回失败,请刷新后重试");
}
/**
* WAIT_REPAIR REPAIRING
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int startRepair(AmsRepairOrder amsRepairOrder, AmsRepairOperateContext operateContext)
{
validateOperateContext(operateContext);
if (amsRepairOrder == null || amsRepairOrder.getRepairId() == null)
{
throw new ServiceException("参数不能为空");
}
// 1. 锁定单据并核查必须处于 WAIT_REPAIR 状态
AmsRepairOrder order = requireOrderForUpdate(amsRepairOrder.getRepairId(), STATUS_WAIT_REPAIR,
"仅待维修状态的报修单允许开始维修");
// 2. 校验维修方信息并填充
if (StringUtils.isEmpty(amsRepairOrder.getRepairerType()))
{
throw new ServiceException("维修方类型不能为空");
}
if (!REPAIRER_TYPE_INTERNAL.equals(amsRepairOrder.getRepairerType())
&& !REPAIRER_TYPE_EXTERNAL.equals(amsRepairOrder.getRepairerType()))
{
throw new ServiceException("非法的维修方类型");
}
if (REPAIRER_TYPE_INTERNAL.equals(amsRepairOrder.getRepairerType()))
{
if (StringUtils.isEmpty(amsRepairOrder.getRepairUserName()))
{
throw new ServiceException("内部维修人不能为空");
}
order.setRepairUserId(amsRepairOrder.getRepairUserId());
order.setRepairUserName(StringUtils.trim(amsRepairOrder.getRepairUserName()));
order.setRepairOrgName(null);
}
else
{
if (StringUtils.isEmpty(amsRepairOrder.getRepairOrgName()))
{
throw new ServiceException("外部维修单位不能为空");
}
order.setRepairUserId(null);
order.setRepairUserName(StringUtils.trim(amsRepairOrder.getRepairUserName())); // 允许外修时选记录人
order.setRepairOrgName(StringUtils.trim(amsRepairOrder.getRepairOrgName()));
}
validateLength(amsRepairOrder.getRepairContactPhone(), 30, "联系电话");
order.setRepairerType(amsRepairOrder.getRepairerType());
order.setRepairContactPhone(StringUtils.trim(amsRepairOrder.getRepairContactPhone()));
// 3. 悲观锁定资产,并校验其前置实物状态
AmsAsset asset = amsAssetMapper.selectAmsAssetByAssetIdForUpdate(order.getAssetId());
if (asset == null || "1".equals(asset.getDelFlag()))
{
throw new ServiceException("资产不存在或已删除");
}
if (!ASSET_STATUS_IN_STOCK.equals(asset.getAssetStatus())
&& !ASSET_STATUS_IN_USE.equals(asset.getAssetStatus()))
{
throw new ServiceException(StringUtils.format("资产【{}】当前状态【{}】不允许进行维修",
asset.getAssetCode(), asset.getAssetStatus()));
}
// 4. 调用公共流转服务执行开始维修 (实物变为 REPAIRING 并写入履历)
AssetTransitionContext context = buildTransitionContext(order, operateContext, "开始资产维修");
AssetTransitionResult transitionResult = assetStatusTransitionService.startRepair(order.getAssetId(), context);
// 5. 记录维修前的资产状态快照,用于恢复
order.setBeforeAssetStatus(transitionResult.getBeforeAsset().getAssetStatus());
order.setRepairStartTime(DateUtils.getNowDate());
order.setOrderStatus(STATUS_REPAIRING);
order.setUpdateBy(operateContext.getOperateLoginName());
order.setUpdateTime(DateUtils.getNowDate());
return updateOrderOrThrow(order, STATUS_WAIT_REPAIR, "报修单开始维修失败,请刷新后重试");
}
/**
* REPAIRING REPAIR_DONE
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int finishRepair(AmsRepairOrder amsRepairOrder, AmsRepairOperateContext operateContext)
{
validateOperateContext(operateContext);
if (amsRepairOrder == null || amsRepairOrder.getRepairId() == null)
{
throw new ServiceException("参数不能为空");
}
// 1. 锁定单据并核查必须处于 REPAIRING 状态
AmsRepairOrder order = requireOrderForUpdate(amsRepairOrder.getRepairId(), STATUS_REPAIRING,
"仅维修中状态的报修单允许提交完成");
// 2. 校验完成信息
if (StringUtils.isEmpty(StringUtils.trim(amsRepairOrder.getRepairResult())))
{
throw new ServiceException("维修结果不能为空");
}
validateLength(amsRepairOrder.getRepairResult(), 500, "维修结果");
if (amsRepairOrder.getRepairCost() == null || amsRepairOrder.getRepairCost().compareTo(BigDecimal.ZERO) < 0)
{
throw new ServiceException("维修费用不能为空且必须为非负数");
}
if (amsRepairOrder.getRepairFinishTime() == null)
{
throw new ServiceException("维修完成时间不能为空");
}
if (order.getRepairStartTime() != null && amsRepairOrder.getRepairFinishTime().before(order.getRepairStartTime()))
{
throw new ServiceException("维修完成时间不能早于维修开始时间");
}
// 3. 悲观锁锁定资产
AmsAsset asset = amsAssetMapper.selectAmsAssetByAssetIdForUpdate(order.getAssetId());
if (asset == null || "1".equals(asset.getDelFlag()))
{
throw new ServiceException("资产不存在或已删除");
}
// 4. 调用公共流转服务执行完成维修 (资产恢复至维修前状态并写履历)
AssetTransitionContext context = buildTransitionContext(order, operateContext, "完成资产维修");
assetStatusTransitionService.finishRepair(order.getAssetId(), order.getBeforeAssetStatus(), context);
// 5. 填充完成属性
order.setRepairFinishTime(amsRepairOrder.getRepairFinishTime());
order.setRepairResult(StringUtils.trim(amsRepairOrder.getRepairResult()));
order.setRepairCost(amsRepairOrder.getRepairCost());
order.setOrderStatus(STATUS_REPAIR_DONE);
order.setUpdateBy(operateContext.getOperateLoginName());
order.setUpdateTime(DateUtils.getNowDate());
return updateOrderOrThrow(order, STATUS_REPAIRING, "报修单完成维修失败,请刷新后重试");
}
/**
* /
*/
@Override
public List<AmsAsset> selectAvailableRepairAssetList(AmsAsset asset, Long currentRepairId)
{
return amsRepairOrderMapper.selectAvailableRepairAssetList(asset, currentRepairId,
Arrays.asList(ASSET_STATUS_IN_STOCK, ASSET_STATUS_IN_USE),
Arrays.asList(STATUS_DRAFT, STATUS_PENDING_CONFIRM, STATUS_WAIT_REPAIR, STATUS_REPAIRING));
}
// ========================================== 私有辅助方法 ==========================================
private AmsRepairOrder requireOrderForUpdate(Long repairId, String expectedStatus, String errMsg)
{
AmsRepairOrder order = amsRepairOrderMapper.selectAmsRepairOrderByRepairIdForUpdate(repairId);
if (order == null)
{
throw new ServiceException("报修单不存在或已删除");
}
if (!expectedStatus.equals(order.getOrderStatus()))
{
throw new ServiceException(errMsg);
}
return order;
}
private int updateOrderOrThrow(AmsRepairOrder order, String expectedStatus, String errorMessage)
{
// 即使当前事务已用 for update 加锁,仍写入期望状态,防止未来绕过锁读路径时误用通用更新 SQL。
order.getParams().put("expectedOrderStatus", expectedStatus);
if (amsRepairOrderMapper.updateAmsRepairOrder(order) != 1)
{
throw new ServiceException(errorMessage);
}
return 1;
}
private AssetTransitionContext buildTransitionContext(AmsRepairOrder order, AmsRepairOperateContext operateContext,
String changeSummary)
{
AssetTransitionContext context = new AssetTransitionContext();
context.setSourceOrderId(order.getRepairId());
context.setSourceOrderNo(order.getRepairNo());
context.setOperateUserId(operateContext.getOperateUserId());
context.setOperateUserName(StringUtils.trim(operateContext.getOperateUserName()));
context.setOperateLoginName(operateContext.getOperateLoginName());
context.setChangeSummary(changeSummary);
context.setRemark(order.getRemark());
return context;
}
private void validateOrderRequest(AmsRepairOrder order)
{
if (order == null)
{
throw new ServiceException("单据数据不能为空");
}
if (order.getAssetId() == null)
{
throw new ServiceException("报修的资产不能为空");
}
if (StringUtils.isEmpty(StringUtils.trim(order.getFaultDesc())))
{
throw new ServiceException("故障描述不能为空");
}
validateLength(order.getFaultDesc(), 500, "故障描述");
validateLength(order.getRemark(), 500, "备注");
if (order.getExpectedFinishTime() != null)
{
Date todayZero = DateUtils.truncate(DateUtils.getNowDate(), java.util.Calendar.DATE);
if (order.getExpectedFinishTime().before(todayZero))
{
throw new ServiceException("期望完成日期不能早于当前日期");
}
}
}
private void fillAssetSnapshots(AmsRepairOrder order, Long currentRepairId)
{
AmsAsset asset = amsAssetMapper.selectAmsAssetByAssetIdForUpdate(order.getAssetId());
if (asset == null || "1".equals(asset.getDelFlag()))
{
throw new ServiceException("所选择的报修资产不存在或已删除");
}
// 1. 实物前置状态必须为在库或在用
if (!ASSET_STATUS_IN_STOCK.equals(asset.getAssetStatus())
&& !ASSET_STATUS_IN_USE.equals(asset.getAssetStatus()))
{
throw new ServiceException(StringUtils.format("资产【{}】当前状态【{}】不允许进行报修",
asset.getAssetCode(), asset.getAssetStatus()));
}
// 2. 校验并发占用
int occupied = amsRepairOrderMapper.countOtherActiveRepairOrderByAssetId(order.getAssetId(), currentRepairId,
Arrays.asList(STATUS_DRAFT, STATUS_PENDING_CONFIRM, STATUS_WAIT_REPAIR, STATUS_REPAIRING));
if (occupied > 0)
{
throw new ServiceException(StringUtils.format("资产【{}】当前已被其他未完结报修单占用", asset.getAssetCode()));
}
// 3. 回填基本属性与仓位/部门快照
order.setAssetCode(asset.getAssetCode());
order.setAssetName(asset.getAssetName());
order.setCategoryId(asset.getCategoryId());
order.setCategoryCode(asset.getCategoryCode());
order.setCategoryName(asset.getCategoryName());
order.setSpecModel(asset.getSpecModel());
order.setBrand(asset.getBrand());
}
private void validateAssetReadyForRepair(Long assetId, Long currentRepairId)
{
AmsAsset asset = amsAssetMapper.selectAmsAssetByAssetIdForUpdate(assetId);
if (asset == null || "1".equals(asset.getDelFlag()))
{
throw new ServiceException("资产不存在或已删除");
}
if (!ASSET_STATUS_IN_STOCK.equals(asset.getAssetStatus())
&& !ASSET_STATUS_IN_USE.equals(asset.getAssetStatus()))
{
throw new ServiceException(StringUtils.format("资产【{}】当前状态不能借由流程流转", asset.getAssetCode()));
}
int occupied = amsRepairOrderMapper.countOtherActiveRepairOrderByAssetId(assetId, currentRepairId,
Arrays.asList(STATUS_DRAFT, STATUS_PENDING_CONFIRM, STATUS_WAIT_REPAIR, STATUS_REPAIRING));
if (occupied > 0)
{
throw new ServiceException(StringUtils.format("资产【{}】已被其他未完成报修单占用", asset.getAssetCode()));
}
}
private void validateLength(String value, int maxLength, String fieldName)
{
if (StringUtils.isNotEmpty(value) && value.length() > maxLength)
{
throw new ServiceException(fieldName + "长度不能超过" + maxLength + "个字符");
}
}
private void validateLoginName(String operateLoginName)
{
if (StringUtils.isEmpty(operateLoginName))
{
throw new ServiceException("操作人账号不能为空");
}
validateLength(operateLoginName, 64, "操作账号");
}
private void validateOperateContext(AmsRepairOperateContext operateContext)
{
if (operateContext == null)
{
throw new ServiceException("操作上下文不能为空");
}
validateOperator(operateContext.getOperateUserId(), operateContext.getOperateUserName(), "操作人");
validateLoginName(operateContext.getOperateLoginName());
}
private void validateOperator(Long operateUserId, String operateUserName, String fieldPrefix)
{
if (operateUserId == null)
{
throw new ServiceException(fieldPrefix + "ID不能为空");
}
if (StringUtils.isEmpty(StringUtils.trim(operateUserName)))
{
throw new ServiceException(fieldPrefix + "名称不能为空");
}
validateLength(operateUserName, 100, fieldPrefix + "名称");
}
}

@ -0,0 +1,303 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.asset.mapper.AmsRepairOrderMapper">
<resultMap type="AmsRepairOrder" id="AmsRepairOrderResult">
<result property="repairId" column="repair_id" />
<result property="repairNo" column="repair_no" />
<result property="assetId" column="asset_id" />
<result property="assetCode" column="asset_code" />
<result property="assetName" column="asset_name" />
<result property="categoryId" column="category_id" />
<result property="categoryCode" column="category_code" />
<result property="categoryName" column="category_name" />
<result property="specModel" column="spec_model" />
<result property="brand" column="brand" />
<result property="reportUserId" column="report_user_id" />
<result property="reportUserName" column="report_user_name" />
<result property="faultDesc" column="fault_desc" />
<result property="reportTime" column="report_time" />
<result property="expectedFinishTime" column="expected_finish_time" />
<result property="repairerType" column="repairer_type" />
<result property="repairUserId" column="repair_user_id" />
<result property="repairUserName" column="repair_user_name" />
<result property="repairOrgName" column="repair_org_name" />
<result property="repairContactPhone" column="repair_contact_phone" />
<result property="repairStartTime" column="repair_start_time" />
<result property="repairFinishTime" column="repair_finish_time" />
<result property="repairResult" column="repair_result" />
<result property="repairCost" column="repair_cost" />
<result property="beforeAssetStatus" column="before_asset_status" />
<result property="rejectReason" column="reject_reason" />
<result property="orderStatus" column="order_status" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
<result property="delFlag" column="del_flag" />
</resultMap>
<resultMap type="AmsAsset" id="AvailableRepairAssetResult">
<result property="assetId" column="asset_id" />
<result property="assetCode" column="asset_code" />
<result property="assetName" column="asset_name" />
<result property="categoryId" column="category_id" />
<result property="categoryCode" column="category_code" />
<result property="categoryName" column="category_name" />
<result property="specModel" column="spec_model" />
<result property="brand" column="brand" />
<result property="assetStatus" column="asset_status" />
<result property="warehouseId" column="warehouse_id" />
<result property="warehouseCode" column="warehouse_code" />
<result property="warehouseName" column="warehouse_name" />
<result property="locationId" column="location_id" />
<result property="locationCode" column="location_code" />
<result property="locationName" column="location_name" />
<result property="tagCode" column="tag_code" />
</resultMap>
<sql id="selectAmsRepairOrderVo">
select repair_id, repair_no, asset_id, asset_code, asset_name, category_id, category_code, category_name, spec_model, brand, report_user_id, report_user_name, fault_desc, report_time, expected_finish_time, repairer_type, repair_user_id, repair_user_name, repair_org_name, repair_contact_phone, repair_start_time, repair_finish_time, repair_result, repair_cost, before_asset_status, reject_reason, order_status, create_by, create_time, update_by, update_time, remark, del_flag from ams_repair_order
</sql>
<select id="selectAmsRepairOrderList" parameterType="AmsRepairOrder" resultMap="AmsRepairOrderResult">
<include refid="selectAmsRepairOrderVo"/>
<where>
del_flag = '0'
<if test="repairNo != null and repairNo != ''"> and repair_no like concat(#{repairNo}, '%')</if>
<if test="assetId != null "> and asset_id = #{assetId}</if>
<if test="assetCode != null and assetCode != ''"> and asset_code like concat(#{assetCode}, '%')</if>
<if test="assetName != null and assetName != ''"> and asset_name like concat('%', #{assetName}, '%')</if>
<if test="categoryId != null "> and category_id = #{categoryId}</if>
<if test="categoryCode != null and categoryCode != ''"> and category_code = #{categoryCode}</if>
<if test="categoryName != null and categoryName != ''"> and category_name like concat('%', #{categoryName}, '%')</if>
<if test="specModel != null and specModel != ''"> and spec_model = #{specModel}</if>
<if test="brand != null and brand != ''"> and brand = #{brand}</if>
<if test="reportUserId != null "> and report_user_id = #{reportUserId}</if>
<if test="reportUserName != null and reportUserName != ''"> and report_user_name like concat('%', #{reportUserName}, '%')</if>
<if test="faultDesc != null and faultDesc != ''"> and fault_desc = #{faultDesc}</if>
<if test="reportTime != null "> and report_time = #{reportTime}</if>
<if test="params.beginReportTime != null and params.beginReportTime != ''">
and report_time &gt;= #{params.beginReportTime}
</if>
<if test="params.endReportTime != null and params.endReportTime != ''">
and report_time &lt; date_add(#{params.endReportTime}, interval 1 day)
</if>
<if test="expectedFinishTime != null "> and expected_finish_time = #{expectedFinishTime}</if>
<if test="repairerType != null and repairerType != ''"> and repairer_type = #{repairerType}</if>
<if test="repairUserId != null "> and repair_user_id = #{repairUserId}</if>
<if test="repairUserName != null and repairUserName != ''"> and repair_user_name like concat('%', #{repairUserName}, '%')</if>
<if test="repairOrgName != null and repairOrgName != ''"> and repair_org_name like concat('%', #{repairOrgName}, '%')</if>
<if test="repairContactPhone != null and repairContactPhone != ''"> and repair_contact_phone = #{repairContactPhone}</if>
<if test="repairStartTime != null "> and repair_start_time = #{repairStartTime}</if>
<if test="repairFinishTime != null "> and repair_finish_time = #{repairFinishTime}</if>
<if test="repairResult != null and repairResult != ''"> and repair_result = #{repairResult}</if>
<if test="repairCost != null "> and repair_cost = #{repairCost}</if>
<if test="beforeAssetStatus != null and beforeAssetStatus != ''"> and before_asset_status = #{beforeAssetStatus}</if>
<if test="rejectReason != null and rejectReason != ''"> and reject_reason = #{rejectReason}</if>
<if test="orderStatus != null and orderStatus != ''"> and order_status = #{orderStatus}</if>
<if test='params.overdueFlag != null and params.overdueFlag == "Y"'>
and expected_finish_time is not null
and expected_finish_time &lt; now()
and order_status != 'REPAIR_DONE'
</if>
<if test='params.overdueFlag != null and params.overdueFlag == "N"'>
and (
expected_finish_time is null
or expected_finish_time &gt;= now()
or order_status = 'REPAIR_DONE'
)
</if>
</where>
order by create_time desc, repair_id desc
</select>
<select id="selectAmsRepairOrderByRepairId" parameterType="Long" resultMap="AmsRepairOrderResult">
<include refid="selectAmsRepairOrderVo"/>
where repair_id = #{repairId} and del_flag = '0'
</select>
<select id="selectAmsRepairOrderByRepairIdForUpdate" parameterType="Long" resultMap="AmsRepairOrderResult">
<include refid="selectAmsRepairOrderVo"/>
where repair_id = #{repairId} and del_flag = '0'
for update
</select>
<insert id="insertAmsRepairOrder" parameterType="AmsRepairOrder" useGeneratedKeys="true" keyProperty="repairId">
insert into ams_repair_order
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="repairNo != null and repairNo != ''">repair_no,</if>
<if test="assetId != null">asset_id,</if>
<if test="assetCode != null and assetCode != ''">asset_code,</if>
<if test="assetName != null and assetName != ''">asset_name,</if>
<if test="categoryId != null">category_id,</if>
<if test="categoryCode != null">category_code,</if>
<if test="categoryName != null">category_name,</if>
<if test="specModel != null">spec_model,</if>
<if test="brand != null">brand,</if>
<if test="reportUserId != null">report_user_id,</if>
<if test="reportUserName != null">report_user_name,</if>
<if test="faultDesc != null">fault_desc,</if>
<if test="reportTime != null">report_time,</if>
<if test="expectedFinishTime != null">expected_finish_time,</if>
<if test="repairerType != null and repairerType != ''">repairer_type,</if>
<if test="repairUserId != null">repair_user_id,</if>
<if test="repairUserName != null">repair_user_name,</if>
<if test="repairOrgName != null">repair_org_name,</if>
<if test="repairContactPhone != null">repair_contact_phone,</if>
<if test="repairStartTime != null">repair_start_time,</if>
<if test="repairFinishTime != null">repair_finish_time,</if>
<if test="repairResult != null">repair_result,</if>
<if test="repairCost != null">repair_cost,</if>
<if test="beforeAssetStatus != null">before_asset_status,</if>
<if test="rejectReason != null">reject_reason,</if>
<if test="orderStatus != null and orderStatus != ''">order_status,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
<if test="remark != null">remark,</if>
<if test="delFlag != null and delFlag != ''">del_flag,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="repairNo != null and repairNo != ''">#{repairNo},</if>
<if test="assetId != null">#{assetId},</if>
<if test="assetCode != null and assetCode != ''">#{assetCode},</if>
<if test="assetName != null and assetName != ''">#{assetName},</if>
<if test="categoryId != null">#{categoryId},</if>
<if test="categoryCode != null">#{categoryCode},</if>
<if test="categoryName != null">#{categoryName},</if>
<if test="specModel != null">#{specModel},</if>
<if test="brand != null">#{brand},</if>
<if test="reportUserId != null">#{reportUserId},</if>
<if test="reportUserName != null">#{reportUserName},</if>
<if test="faultDesc != null">#{faultDesc},</if>
<if test="reportTime != null">#{reportTime},</if>
<if test="expectedFinishTime != null">#{expectedFinishTime},</if>
<if test="repairerType != null and repairerType != ''">#{repairerType},</if>
<if test="repairUserId != null">#{repairUserId},</if>
<if test="repairUserName != null">#{repairUserName},</if>
<if test="repairOrgName != null">#{repairOrgName},</if>
<if test="repairContactPhone != null">#{repairContactPhone},</if>
<if test="repairStartTime != null">#{repairStartTime},</if>
<if test="repairFinishTime != null">#{repairFinishTime},</if>
<if test="repairResult != null">#{repairResult},</if>
<if test="repairCost != null">#{repairCost},</if>
<if test="beforeAssetStatus != null">#{beforeAssetStatus},</if>
<if test="rejectReason != null">#{rejectReason},</if>
<if test="orderStatus != null and orderStatus != ''">#{orderStatus},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="remark != null">#{remark},</if>
<if test="delFlag != null and delFlag != ''">#{delFlag},</if>
</trim>
</insert>
<update id="updateAmsRepairOrder" parameterType="AmsRepairOrder">
update ams_repair_order
set repair_no = #{repairNo},
asset_id = #{assetId},
asset_code = #{assetCode},
asset_name = #{assetName},
category_id = #{categoryId},
category_code = #{categoryCode},
category_name = #{categoryName},
spec_model = #{specModel},
brand = #{brand},
report_user_id = #{reportUserId},
report_user_name = #{reportUserName},
fault_desc = #{faultDesc},
report_time = #{reportTime},
expected_finish_time = #{expectedFinishTime},
repairer_type = #{repairerType},
repair_user_id = #{repairUserId},
repair_user_name = #{repairUserName},
repair_org_name = #{repairOrgName},
repair_contact_phone = #{repairContactPhone},
repair_start_time = #{repairStartTime},
repair_finish_time = #{repairFinishTime},
repair_result = #{repairResult},
repair_cost = #{repairCost},
before_asset_status = #{beforeAssetStatus},
reject_reason = #{rejectReason},
order_status = #{orderStatus},
create_by = #{createBy},
create_time = #{createTime},
update_by = #{updateBy},
update_time = #{updateTime},
remark = #{remark},
del_flag = #{delFlag}
where repair_id = #{repairId} and del_flag = '0'
<if test="params.expectedOrderStatus != null and params.expectedOrderStatus != ''">
and order_status = #{params.expectedOrderStatus}
</if>
</update>
<!-- 逻辑删除:仅草稿单允许删除 -->
<update id="deleteAmsRepairOrderByRepairId" parameterType="Long">
update ams_repair_order set del_flag = '1' where repair_id = #{repairId} and del_flag = '0' and order_status = 'DRAFT'
</update>
<update id="deleteAmsRepairOrderByRepairIds" parameterType="String">
update ams_repair_order set del_flag = '1' where repair_id in
<foreach item="repairId" collection="array" open="(" separator="," close=")">
#{repairId}
</foreach>
and del_flag = '0' and order_status = 'DRAFT'
</update>
<select id="countOtherActiveRepairOrderByAssetId" resultType="int">
select count(1)
from ams_repair_order
where asset_id = #{assetId}
and del_flag = '0'
and order_status in
<foreach item="status" collection="statuses" open="(" separator="," close=")">
#{status}
</foreach>
<if test="currentRepairId != null">
and repair_id != #{currentRepairId}
</if>
</select>
<select id="selectAvailableRepairAssetList" resultMap="AvailableRepairAssetResult">
select asset.asset_id, asset.asset_code, asset.asset_name, asset.category_id,
asset.category_code, asset.category_name, asset.spec_model, asset.brand,
asset.asset_status, asset.warehouse_id, asset.warehouse_code, asset.warehouse_name,
asset.location_id, asset.location_code, asset.location_name, asset.tag_code
from ams_asset asset
where asset.del_flag = '0'
and asset.asset_status in
<foreach item="status" collection="statuses" open="(" separator="," close=")">
#{status}
</foreach>
and not exists (
select 1
from ams_repair_order r
where r.asset_id = asset.asset_id
and r.del_flag = '0'
and r.order_status in
<foreach item="rStatus" collection="activeRepairStatuses" open="(" separator="," close=")">
#{rStatus}
</foreach>
<if test="currentRepairId != null">
and r.repair_id != #{currentRepairId}
</if>
)
<if test="asset.assetCode != null and asset.assetCode != ''">
and asset.asset_code like concat('%', #{asset.assetCode}, '%')
</if>
<if test="asset.assetName != null and asset.assetName != ''">
and asset.asset_name like concat('%', #{asset.assetName}, '%')
</if>
<if test="asset.categoryName != null and asset.categoryName != ''">
and asset.category_name like concat('%', #{asset.categoryName}, '%')
</if>
order by asset.asset_id
</select>
</mapper>

@ -0,0 +1,174 @@
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
<head>
<th:block th:include="include :: header('新增报修维修单')" />
<th:block th:include="include :: datetimepicker-css" />
</head>
<body class="white-bg">
<div class="wrapper wrapper-content animated fadeInRight ibox-content">
<form class="form-horizontal m" id="form-repair-add">
<h4 class="form-header h4">基本信息</h4>
<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label is-required">选择资产:</label>
<div class="col-sm-8">
<div class="input-group">
<input id="assetId" name="assetId" type="hidden" required>
<input id="assetCode" name="assetCode" class="form-control" type="text" placeholder="请选择报修资产" readonly required>
<span class="input-group-btn">
<button type="button" class="btn btn-primary" onclick="selectAsset()"><i class="fa fa-search"></i> 选择</button>
</span>
</div>
</div>
</div>
</div>
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label">资产名称:</label>
<div class="col-sm-8">
<input id="assetName" name="assetName" class="form-control" type="text" readonly>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label">资产类别:</label>
<div class="col-sm-8">
<input id="categoryName" name="categoryName" class="form-control" type="text" readonly>
</div>
</div>
</div>
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label">规格型号:</label>
<div class="col-sm-8">
<input id="specModel" name="specModel" class="form-control" type="text" readonly>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label">品牌:</label>
<div class="col-sm-8">
<input id="brand" name="brand" class="form-control" type="text" readonly>
</div>
</div>
</div>
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label is-required">预计完成日期:</label>
<div class="col-sm-8">
<div class="input-group date">
<input name="expectedFinishTime" class="form-control" placeholder="yyyy-MM-dd" type="text" required>
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label">报修人:</label>
<div class="col-sm-8">
<input name="reportUserId" th:value="${defaultReportUserId}" type="hidden">
<input name="reportUserName" th:value="${defaultReportUserName}" class="form-control" type="text" readonly>
</div>
</div>
</div>
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label">报修时间:</label>
<div class="col-sm-8">
<input name="reportTime" th:value="${#dates.format(#dates.createNow(), 'yyyy-MM-dd HH:mm:ss')}" class="form-control" type="text" readonly>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="form-group">
<label class="col-sm-2 control-label is-required">故障描述:</label>
<div class="col-sm-10">
<textarea name="faultDesc" maxlength="500" class="form-control" rows="3" required placeholder="请详细描述资产故障表现..."></textarea>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="form-group">
<label class="col-sm-2 control-label">备注:</label>
<div class="col-sm-10">
<textarea name="remark" maxlength="500" class="form-control" rows="2" placeholder="备注信息..."></textarea>
</div>
</div>
</div>
</div>
</form>
</div>
<th:block th:include="include :: footer" />
<th:block th:include="include :: datetimepicker-js" />
<script th:inline="javascript">
var prefix = ctx + "asset/repair"
$("#form-repair-add").validate({
focusCleanup: true
});
function submitHandler() {
if ($.validate.form()) {
$.operate.save(prefix + "/add", $('#form-repair-add').serialize());
}
}
function selectAsset() {
var url = prefix + "/selectAsset";
$.modal.openOptions({
title: "选择可报修资产",
url: url,
width: "1200",
height: "680",
callBack: function(index, layero) {
var iframeWindow = layero.find("iframe")[0].contentWindow;
var selected = iframeWindow.getSelectedAssets();
if (!selected || selected.length === 0) {
$.modal.alertWarning("请选择一条资产记录");
return;
}
if (selected.length > 1) {
$.modal.alertWarning("一期报修仅支持一单一资产,请只选择一条资产");
return;
}
var asset = selected[0];
$("#assetId").val(asset.assetId);
$("#assetCode").val(asset.assetCode);
$("#assetName").val(asset.assetName);
$("#categoryName").val(asset.categoryName);
$("#specModel").val(asset.specModel || "-");
$("#brand").val(asset.brand || "-");
$.modal.close(index);
}
});
}
$("input[name='expectedFinishTime']").datetimepicker({
format: "yyyy-mm-dd",
minView: "month",
todayBtn: true,
autoclose: true,
startDate: new Date() // 不能选择早于当天的日期
});
</script>
</body>
</html>

@ -0,0 +1,187 @@
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
<head>
<th:block th:include="include :: header('修改报修维修单')" />
<th:block th:include="include :: datetimepicker-css" />
</head>
<body class="white-bg">
<div class="wrapper wrapper-content animated fadeInRight ibox-content">
<form class="form-horizontal m" id="form-repair-edit" th:object="${amsRepairOrder}">
<input name="repairId" th:field="*{repairId}" type="hidden">
<h4 class="form-header h4">基本信息</h4>
<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label">报修单号:</label>
<div class="col-sm-8">
<input name="repairNo" th:field="*{repairNo}" class="form-control" type="text" readonly>
</div>
</div>
</div>
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label is-required">选择资产:</label>
<div class="col-sm-8">
<div class="input-group">
<input id="assetId" name="assetId" th:field="*{assetId}" type="hidden" required>
<input id="assetCode" name="assetCode" th:field="*{assetCode}" class="form-control" type="text" placeholder="请选择报修资产" readonly required>
<span class="input-group-btn">
<button type="button" class="btn btn-primary" onclick="selectAsset()"><i class="fa fa-search"></i> 选择</button>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label">资产名称:</label>
<div class="col-sm-8">
<input id="assetName" name="assetName" th:field="*{assetName}" class="form-control" type="text" readonly>
</div>
</div>
</div>
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label">资产类别:</label>
<div class="col-sm-8">
<input id="categoryName" name="categoryName" th:field="*{categoryName}" class="form-control" type="text" readonly>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label">规格型号:</label>
<div class="col-sm-8">
<input id="specModel" name="specModel" th:field="*{specModel}" class="form-control" type="text" readonly>
</div>
</div>
</div>
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label">品牌:</label>
<div class="col-sm-8">
<input id="brand" name="brand" th:field="*{brand}" class="form-control" type="text" readonly>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label is-required">预计完成日期:</label>
<div class="col-sm-8">
<div class="input-group date">
<input name="expectedFinishTime" th:value="${#dates.format(amsRepairOrder.expectedFinishTime, 'yyyy-MM-dd')}" class="form-control" placeholder="yyyy-MM-dd" type="text" required>
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
</div>
</div>
</div>
</div>
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label">报修人:</label>
<div class="col-sm-8">
<input name="reportUserId" th:field="*{reportUserId}" type="hidden">
<input name="reportUserName" th:field="*{reportUserName}" class="form-control" type="text" readonly>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label">报修时间:</label>
<div class="col-sm-8">
<input name="reportTime" th:value="${#dates.format(amsRepairOrder.reportTime, 'yyyy-MM-dd HH:mm:ss')}" class="form-control" type="text" readonly>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="form-group">
<label class="col-sm-2 control-label is-required">故障描述:</label>
<div class="col-sm-10">
<textarea name="faultDesc" maxlength="500" class="form-control" rows="3" required placeholder="请详细描述资产故障表现...">[[*{faultDesc}]]</textarea>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="form-group">
<label class="col-sm-2 control-label">备注:</label>
<div class="col-sm-10">
<textarea name="remark" maxlength="500" class="form-control" rows="2" placeholder="备注信息...">[[*{remark}]]</textarea>
</div>
</div>
</div>
</div>
</form>
</div>
<th:block th:include="include :: footer" />
<th:block th:include="include :: datetimepicker-js" />
<script th:inline="javascript">
var prefix = ctx + "asset/repair";
$("#form-repair-edit").validate({
focusCleanup: true
});
function submitHandler() {
if ($.validate.form()) {
$.operate.save(prefix + "/edit", $('#form-repair-edit').serialize());
}
}
function selectAsset() {
var repairId = $("input[name='repairId']").val();
var url = prefix + "/selectAsset?repairId=" + repairId;
$.modal.openOptions({
title: "选择可报修资产",
url: url,
width: "1200",
height: "680",
callBack: function(index, layero) {
var iframeWindow = layero.find("iframe")[0].contentWindow;
var selected = iframeWindow.getSelectedAssets();
if (!selected || selected.length === 0) {
$.modal.alertWarning("请选择一条资产记录");
return;
}
if (selected.length > 1) {
$.modal.alertWarning("一期报修仅支持一单一资产,请只选择一条资产");
return;
}
var asset = selected[0];
$("#assetId").val(asset.assetId);
$("#assetCode").val(asset.assetCode);
$("#assetName").val(asset.assetName);
$("#categoryName").val(asset.categoryName);
$("#specModel").val(asset.specModel || "-");
$("#brand").val(asset.brand || "-");
$.modal.close(index);
}
});
}
$("input[name='expectedFinishTime']").datetimepicker({
format: "yyyy-mm-dd",
minView: "month",
todayBtn: true,
autoclose: true,
startDate: new Date()
});
</script>
</body>
</html>

@ -0,0 +1,81 @@
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
<head>
<th:block th:include="include :: header('完成维修录入')" />
<th:block th:include="include :: datetimepicker-css" />
</head>
<body class="white-bg">
<div class="wrapper wrapper-content animated fadeInRight ibox-content">
<form class="form-horizontal m" id="form-repair-finish" th:object="${amsRepairOrder}">
<input name="repairId" th:field="*{repairId}" type="hidden">
<!-- 隐藏或只读展示开始时间,用来做比较 -->
<input id="repairStartTimeStr" type="hidden" th:value="${#dates.format(amsRepairOrder.repairStartTime, 'yyyy-MM-dd')}">
<div class="form-group">
<label class="col-sm-3 control-label">维修开始时间:</label>
<div class="col-sm-8">
<input class="form-control" type="text" th:value="${#dates.format(amsRepairOrder.repairStartTime, 'yyyy-MM-dd')}" readonly>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label is-required">维修完成时间:</label>
<div class="col-sm-8">
<div class="input-group date">
<input id="repairFinishTime" name="repairFinishTime" class="form-control" placeholder="yyyy-MM-dd" type="text" required>
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label is-required">维修费用(元)</label>
<div class="col-sm-8">
<input name="repairCost" class="form-control" type="number" min="0" step="0.01" placeholder="请填写非负的维修金额" required>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label is-required">维修结果:</label>
<div class="col-sm-8">
<textarea name="repairResult" maxlength="500" class="form-control" rows="3" required placeholder="请详细描述维修成果、更换了哪些部件等..."></textarea>
</div>
</div>
</form>
</div>
<th:block th:include="include :: footer" />
<th:block th:include="include :: datetimepicker-js" />
<script th:inline="javascript">
var prefix = ctx + "asset/repair";
$("#form-repair-finish").validate({
focusCleanup: true
});
function submitHandler() {
if ($.validate.form()) {
var startTimeStr = $("#repairStartTimeStr").val();
var finishTimeStr = $("#repairFinishTime").val();
if (startTimeStr && finishTimeStr) {
var startTime = new Date(startTimeStr.replace(/-/g, "/"));
var finishTime = new Date(finishTimeStr.replace(/-/g, "/"));
if (finishTime < startTime) {
$.modal.alertWarning("完成时间不能早于开始时间(" + startTimeStr + "");
return;
}
}
$.operate.save(prefix + "/finish", $('#form-repair-finish').serialize());
}
}
$("input[name='repairFinishTime']").datetimepicker({
format: "yyyy-mm-dd",
minView: "month",
todayBtn: true,
autoclose: true
});
</script>
</body>
</html>

@ -0,0 +1,252 @@
<!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('报修维修管理列表')" />
</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="repairNo"/>
</li>
<li>
<label>资产编码:</label>
<input type="text" name="assetCode"/>
</li>
<li>
<label>资产名称:</label>
<input type="text" name="assetName"/>
</li>
<li>
<label>报修人:</label>
<input type="text" name="reportUserName"/>
</li>
<li class="select-time">
<label>报修时间:</label>
<input type="text" class="time-input" id="startTime" placeholder="开始时间" name="params[beginReportTime]"/>
<span>-</span>
<input type="text" class="time-input" id="endTime" placeholder="结束时间" name="params[endReportTime]"/>
</li>
<li>
<label>维修方:</label>
<select name="repairerType" th:with="type=${@dict.getType('ams_repairer_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('ams_repair_status')}">
<option value="">所有</option>
<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
</select>
</li>
<li>
<label>是否超期:</label>
<select name="params[overdueFlag]">
<option value="">所有</option>
<option value="Y">已超期</option>
<option value="N">未超期</option>
</select>
</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-success" onclick="$.operate.add()" shiro:hasPermission="asset:repair:add">
<i class="fa fa-plus"></i> 新增报修
</a>
<a class="btn btn-warning" onclick="$.table.exportExcel()" shiro:hasPermission="asset:repair: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 editFlag = [[${@permission.hasPermi('asset:repair:edit')}]];
var removeFlag = [[${@permission.hasPermi('asset:repair:remove')}]];
var submitFlag = [[${@permission.hasPermi('asset:repair:submit')}]];
var confirmFlag = [[${@permission.hasPermi('asset:repair:confirm')}]];
var rejectFlag = [[${@permission.hasPermi('asset:repair:reject')}]];
var startFlag = [[${@permission.hasPermi('asset:repair:start')}]];
var finishFlag = [[${@permission.hasPermi('asset:repair:finish')}]];
var repairerTypeDatas = [[${@dict.getType('ams_repairer_type')}]];
var orderStatusDatas = [[${@dict.getType('ams_repair_status')}]];
var prefix = ctx + "asset/repair";
$(function() {
var options = {
url: prefix + "/list",
viewUrl: prefix + "/view/{id}",
createUrl: prefix + "/add",
updateUrl: prefix + "/edit/{id}",
removeUrl: prefix + "/remove",
exportUrl: prefix + "/export",
modalName: "报修维修单",
columns: [{
checkbox: true
},
{
field: 'repairId',
title: '维修单ID',
visible: false
},
{
field: 'repairNo',
title: '报修单号'
},
{
field: 'assetCode',
title: '资产编码'
},
{
field: 'assetName',
title: '资产名称'
},
{
field: 'categoryName',
title: '资产类别'
},
{
field: 'specModel',
title: '规格型号',
visible: false
},
{
field: 'brand',
title: '品牌',
visible: false
},
{
field: 'reportUserName',
title: '报修人'
},
{
field: 'faultDesc',
title: '故障描述',
formatter: function(value, row, index) {
return $.table.tooltip(value, 15);
}
},
{
field: 'reportTime',
title: '报修时间'
},
{
field: 'expectedFinishTime',
title: '预计完成时间',
visible: false
},
{
field: 'repairerType',
title: '维修方',
formatter: function(value, row, index) {
return $.table.selectDictLabel(repairerTypeDatas, value) || "-";
}
},
{
field: 'repairUserName',
title: '维修人'
},
{
field: 'repairOrgName',
title: '维修单位',
visible: false
},
{
field: 'repairStartTime',
title: '开始时间',
visible: false
},
{
field: 'repairFinishTime',
title: '完成时间'
},
{
field: 'repairCost',
title: '维修费用'
},
{
field: 'orderStatus',
title: '单据状态',
formatter: function(value, row, index) {
return $.table.selectDictLabel(orderStatusDatas, value);
}
},
{
field: 'rejectReason',
title: '驳回原因',
visible: false
},
{
title: '操作',
align: 'center',
formatter: function(value, row, index) {
var actions = [];
actions.push('<a class="btn btn-info btn-xs" href="javascript:void(0)" onclick="$.operate.view(\'' + row.repairId + '\')"><i class="fa fa-eye"></i>查看</a> ');
if (row.orderStatus === "DRAFT") {
actions.push('<a class="btn btn-success btn-xs ' + editFlag + '" href="javascript:void(0)" onclick="$.operate.edit(\'' + row.repairId + '\')"><i class="fa fa-edit"></i>编辑</a> ');
actions.push('<a class="btn btn-primary btn-xs ' + submitFlag + '" href="javascript:void(0)" onclick="submitRepair(\'' + row.repairId + '\')"><i class="fa fa-upload"></i>提交</a> ');
actions.push('<a class="btn btn-danger btn-xs ' + removeFlag + '" href="javascript:void(0)" onclick="$.operate.remove(\'' + row.repairId + '\')"><i class="fa fa-remove"></i>删除</a>');
} else if (row.orderStatus === "PENDING_CONFIRM") {
actions.push('<a class="btn btn-primary btn-xs ' + confirmFlag + '" href="javascript:void(0)" onclick="confirmRepair(\'' + row.repairId + '\')"><i class="fa fa-check"></i>确认受理</a> ');
actions.push('<a class="btn btn-warning btn-xs ' + rejectFlag + '" href="javascript:void(0)" onclick="rejectRepair(\'' + row.repairId + '\')"><i class="fa fa-reply"></i>驳回</a>');
} else if (row.orderStatus === "WAIT_REPAIR") {
actions.push('<a class="btn btn-primary btn-xs ' + startFlag + '" href="javascript:void(0)" onclick="startRepair(\'' + row.repairId + '\')"><i class="fa fa-wrench"></i>开始维修</a>');
} else if (row.orderStatus === "REPAIRING") {
actions.push('<a class="btn btn-primary btn-xs ' + finishFlag + '" href="javascript:void(0)" onclick="finishRepair(\'' + row.repairId + '\')"><i class="fa fa-check-square-o"></i>完成维修</a>');
}
return actions.join('');
}
}]
};
$.table.init(options);
});
function submitRepair(repairId) {
$.modal.confirm("提交后报修单不可再修改或删除,是否继续?", function() {
$.operate.post(prefix + "/submit/" + repairId, {});
});
}
function confirmRepair(repairId) {
$.modal.confirm("您确定要受理该报修申请吗?", function() {
$.operate.post(prefix + "/confirm/" + repairId, {});
});
}
function rejectRepair(repairId) {
layer.prompt({ title: "请输入驳回原因", formType: 2, maxlength: 500 }, function(text, index) {
layer.close(index);
$.operate.post(prefix + "/reject/" + repairId, { rejectReason: text });
});
}
function startRepair(repairId) {
$.modal.open("开始资产维修", prefix + "/start/" + repairId, "800", "500");
}
function finishRepair(repairId) {
$.modal.open("完成资产维修", prefix + "/finish/" + repairId, "800", "500");
}
</script>
</body>
</html>

@ -0,0 +1,81 @@
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:include="include :: header('选择可报修资产')" />
</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="assetCode"></li>
<li><label>资产名称:</label><input type="text" name="assetName"></li>
<li><label>资产类别:</label><input type="text" name="categoryName"></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="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 prefix = ctx + "asset/repair";
var repairId = [[${repairId}]];
$(function() {
var url = prefix + "/availableAssetList";
if (repairId) {
url += "?repairId=" + repairId;
}
$.table.init({
url: url,
showSearch: false,
showRefresh: true,
showToggle: false,
showColumns: false,
modalName: "可报修资产",
columns: [
{ checkbox: true },
{ field: "assetCode", title: "资产编码" },
{ field: "assetName", title: "资产名称" },
{ field: "categoryName", title: "资产类别" },
{ field: "specModel", title: "规格型号", formatter: function(value) { return value || "-"; } },
{ field: "brand", title: "品牌", formatter: function(value) { return value || "-"; } },
{
field: "assetStatus",
title: "资产状态",
formatter: function(value, row, index) {
if (value === 'IN_STOCK') {
return '<span class="label label-primary">在库</span>';
} else if (value === 'IN_USE') {
return '<span class="label label-info">在用</span>';
}
return value;
}
},
{ field: "warehouseName", title: "当前仓库", formatter: function(value) { return value || "-"; } },
{ field: "locationName", title: "当前位置", formatter: function(value) { return value || "-"; } },
{ field: "tagCode", title: "RFID标签", formatter: function(value) { return value || "-"; } }
]
});
});
function getSelectedAssets() {
return $("#bootstrap-table").bootstrapTable("getSelections");
}
</script>
</body>
</html>

@ -0,0 +1,130 @@
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
<head>
<th:block th:include="include :: header('开始维修录入')" />
</head>
<body class="white-bg">
<div class="wrapper wrapper-content animated fadeInRight ibox-content">
<form class="form-horizontal m" id="form-repair-start" th:object="${amsRepairOrder}">
<input name="repairId" th:field="*{repairId}" type="hidden">
<div class="form-group">
<label class="col-sm-3 control-label is-required">维修方类型:</label>
<div class="col-sm-8">
<select id="repairerType" name="repairerType" class="form-control" th:with="type=${@dict.getType('ams_repairer_type')}" required>
<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
</select>
</div>
</div>
<!-- 内修展示 -->
<div class="form-group" id="div-internal-repairer">
<label class="col-sm-3 control-label is-required">维修人:</label>
<div class="col-sm-8">
<select id="repairUserId" name="repairUserId" class="form-control">
<option value="">-- 请选择系统用户 --</option>
<option th:each="user : ${userList}" th:text="${user.userName}" th:value="${user.userId}" th:data-name="${user.userName}" th:data-phone="${user.phonenumber}"></option>
</select>
<input id="manualRepairUserName" class="form-control" type="text" placeholder="或手工填写内部维修人姓名" style="margin-top: 8px;">
<input type="hidden" id="repairUserName" name="repairUserName">
</div>
</div>
<!-- 外修展示 -->
<div class="form-group" id="div-external-org" style="display: none;">
<label class="col-sm-3 control-label is-required">外部维修单位:</label>
<div class="col-sm-8">
<input id="repairOrgName" name="repairOrgName" class="form-control" type="text" placeholder="请输入外部维修公司名称">
</div>
</div>
<div class="form-group" id="div-external-repairer" style="display: none;">
<label class="col-sm-3 control-label">维修联系人:</label>
<div class="col-sm-8">
<input id="externalRepairUserName" name="externalRepairUserName" class="form-control" type="text" placeholder="请输入维修人员姓名">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">联系电话:</label>
<div class="col-sm-8">
<input id="repairContactPhone" name="repairContactPhone" class="form-control" type="text" placeholder="请输入联系电话">
</div>
</div>
</form>
</div>
<th:block th:include="include :: footer" />
<script th:inline="javascript">
var prefix = ctx + "asset/repair";
$("#form-repair-start").validate({
focusCleanup: true
});
function submitHandler() {
if ($.validate.form()) {
var repairerType = $("#repairerType").val();
if (repairerType === 'INTERNAL') {
var userId = $("#repairUserId").val();
var userName = userId ? $("#repairUserId").find("option:selected").data("name") : $("#manualRepairUserName").val();
if (!userName) {
$.modal.alertWarning("请选择系统用户或填写内部维修人姓名");
return;
}
$("#repairUserName").val(userName);
} else if (repairerType === 'EXTERNAL') {
var orgName = $("#repairOrgName").val();
if (!orgName) {
$.modal.alertWarning("请输入外部维修单位");
return;
}
var extUserName = $("#externalRepairUserName").val();
$("#repairUserName").val(extUserName);
}
$.operate.save(prefix + "/start", $('#form-repair-start').serialize());
}
}
$(function() {
$("#repairerType").change(function() {
var type = $(this).val();
if (type === 'INTERNAL') {
$("#div-internal-repairer").show();
$("#div-external-org").hide();
$("#div-external-repairer").hide();
// 清空外修输入,并清空联系电话(切换时重置)
$("#repairOrgName").val("");
$("#externalRepairUserName").val("");
$("#repairContactPhone").val("");
} else {
$("#div-internal-repairer").hide();
$("#div-external-org").show();
$("#div-external-repairer").show();
// 清空内修输入,并清空联系电话(切换时重置)
$("#repairUserId").val("");
$("#manualRepairUserName").val("");
$("#repairUserName").val("");
$("#repairContactPhone").val("");
}
});
// 监听内部维修人选择,自动带出表 sys_user 中该用户的手机号码
$("#repairUserId").change(function() {
var phone = $(this).find("option:selected").data("phone");
if (phone) {
$("#repairContactPhone").val(phone);
} else {
$("#repairContactPhone").val("");
}
});
// 初始化触发
$("#repairerType").trigger("change");
$("#repairUserId").trigger("change");
});
</script>
</body>
</html>

@ -0,0 +1,244 @@
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
<head>
<th:block th:include="include :: header('报修维修单详细')" />
</head>
<body class="white-bg">
<div class="wrapper wrapper-content animated fadeInRight ibox-content">
<form class="form-horizontal" th:object="${amsRepairOrder}">
<h4 class="form-header h4">基本信息</h4>
<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label">报修单号:</label>
<div class="col-sm-8">
<p class="form-control-plaintext" th:text="*{repairNo}"></p>
</div>
</div>
</div>
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label">单据状态:</label>
<div class="col-sm-8">
<p class="form-control-plaintext">
<span class="label label-primary" th:if="*{orderStatus == 'DRAFT'}">草稿</span>
<span class="label label-warning" th:if="*{orderStatus == 'PENDING_CONFIRM'}">待受理</span>
<span class="label label-danger" th:if="*{orderStatus == 'REJECTED'}">已驳回</span>
<span class="label label-info" th:if="*{orderStatus == 'WAIT_REPAIR'}">待维修</span>
<span class="label label-info" th:if="*{orderStatus == 'REPAIRING'}">维修中</span>
<span class="label label-success" th:if="*{orderStatus == 'REPAIR_DONE'}">维修完成</span>
</p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label">资产编码:</label>
<div class="col-sm-8">
<p class="form-control-plaintext" th:text="*{assetCode}"></p>
</div>
</div>
</div>
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label">资产名称:</label>
<div class="col-sm-8">
<p class="form-control-plaintext" th:text="*{assetName}"></p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label">资产类别:</label>
<div class="col-sm-8">
<p class="form-control-plaintext" th:text="*{categoryName}"></p>
</div>
</div>
</div>
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label">规格型号:</label>
<div class="col-sm-8">
<p class="form-control-plaintext" th:text="*{specModel ?: '-'}"></p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label">品牌:</label>
<div class="col-sm-8">
<p class="form-control-plaintext" th:text="*{brand ?: '-'}"></p>
</div>
</div>
</div>
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label">报修前资产状态:</label>
<div class="col-sm-8">
<p class="form-control-plaintext">
<span th:if="*{beforeAssetStatus == 'IN_STOCK'}" class="label label-primary">在库</span>
<span th:if="*{beforeAssetStatus == 'IN_USE'}" class="label label-info">在用</span>
<span th:if="*{beforeAssetStatus == null}">-</span>
</p>
</div>
</div>
</div>
</div>
<h4 class="form-header h4">故障及申报信息</h4>
<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label">报修人:</label>
<div class="col-sm-8">
<p class="form-control-plaintext" th:text="*{reportUserName}"></p>
</div>
</div>
</div>
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label">报修时间:</label>
<div class="col-sm-8">
<p class="form-control-plaintext" th:text="*{#dates.format(reportTime, 'yyyy-MM-dd HH:mm:ss')}"></p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label">预计完成日期:</label>
<div class="col-sm-8">
<p class="form-control-plaintext" th:text="*{#dates.format(expectedFinishTime, 'yyyy-MM-dd')}"></p>
</div>
</div>
</div>
<div class="col-xs-6" th:if="*{orderStatus == 'REJECTED'}">
<div class="form-group">
<label class="col-sm-4 control-label text-danger">驳回原因:</label>
<div class="col-sm-8">
<p class="form-control-plaintext text-danger" th:text="*{rejectReason}"></p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="form-group">
<label class="col-sm-2 control-label">故障描述:</label>
<div class="col-sm-10">
<p class="form-control-plaintext" style="white-space: pre-wrap;" th:text="*{faultDesc}"></p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="form-group">
<label class="col-sm-2 control-label">备注:</label>
<div class="col-sm-10">
<p class="form-control-plaintext" th:text="*{remark ?: '-'}"></p>
</div>
</div>
</div>
</div>
<!-- 仅在进入维修环节后展示 -->
<div th:if="*{orderStatus == 'REPAIRING' || orderStatus == 'REPAIR_DONE'}">
<h4 class="form-header h4">维修过程及结果</h4>
<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label">维修方类型:</label>
<div class="col-sm-8">
<p class="form-control-plaintext" th:text="*{repairerType == 'INTERNAL' ? '内部维修' : '外部维修'}"></p>
</div>
</div>
</div>
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label" th:text="*{repairerType == 'INTERNAL' ? '维修人:' : '外部维修单位:'}"></label>
<div class="col-sm-8">
<p class="form-control-plaintext" th:text="*{repairerType == 'INTERNAL' ? repairUserName : repairOrgName}"></p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-6" th:if="*{repairerType == 'EXTERNAL' && repairUserName != null && repairUserName != ''}">
<div class="form-group">
<label class="col-sm-4 control-label">维修联系人:</label>
<div class="col-sm-8">
<p class="form-control-plaintext" th:text="*{repairUserName}"></p>
</div>
</div>
</div>
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label">联系电话:</label>
<div class="col-sm-8">
<p class="form-control-plaintext" th:text="*{repairContactPhone ?: '-'}"></p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label">维修开始时间:</label>
<div class="col-sm-8">
<p class="form-control-plaintext" th:text="*{#dates.format(repairStartTime, 'yyyy-MM-dd HH:mm:ss')}"></p>
</div>
</div>
</div>
<div class="col-xs-6" th:if="*{orderStatus == 'REPAIR_DONE'}">
<div class="form-group">
<label class="col-sm-4 control-label">维修完成时间:</label>
<div class="col-sm-8">
<p class="form-control-plaintext" th:text="*{#dates.format(repairFinishTime, 'yyyy-MM-dd HH:mm:ss')}"></p>
</div>
</div>
</div>
</div>
<div class="row" th:if="*{orderStatus == 'REPAIR_DONE'}">
<div class="col-xs-6">
<div class="form-group">
<label class="col-sm-4 control-label">维修费用:</label>
<div class="col-sm-8">
<p class="form-control-plaintext" th:text="*{'¥' + #numbers.formatDecimal(repairCost, 1, 2)}"></p>
</div>
</div>
</div>
</div>
<div class="row" th:if="*{orderStatus == 'REPAIR_DONE'}">
<div class="col-xs-12">
<div class="form-group">
<label class="col-sm-2 control-label">维修结果:</label>
<div class="col-sm-10">
<p class="form-control-plaintext" style="white-space: pre-wrap;" th:text="*{repairResult}"></p>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
<th:block th:include="include :: footer" />
</body>
</html>

@ -0,0 +1,492 @@
package com.ruoyi.asset.service.impl;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.math.BigDecimal;
import java.util.Calendar;
import java.util.Date;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import com.ruoyi.asset.domain.AmsAsset;
import com.ruoyi.asset.domain.AmsRepairOperateContext;
import com.ruoyi.asset.domain.AmsRepairOrder;
import com.ruoyi.asset.domain.AssetTransitionContext;
import com.ruoyi.asset.domain.AssetTransitionResult;
import com.ruoyi.asset.mapper.AmsAssetMapper;
import com.ruoyi.asset.mapper.AmsRepairOrderMapper;
import com.ruoyi.asset.service.IAssetStatusTransitionService;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.system.service.ISysCodeRuleService;
/**
*
*
*
*
* @author Yangk
* @date 2026-06-16
*/
@ExtendWith(MockitoExtension.class)
class AmsRepairOrderServiceImplTest
{
@Mock
private AmsRepairOrderMapper amsRepairOrderMapper;
@Mock
private AmsAssetMapper amsAssetMapper;
@Mock
private ISysCodeRuleService sysCodeRuleService;
@Mock
private IAssetStatusTransitionService assetStatusTransitionService;
@InjectMocks
private AmsRepairOrderServiceImpl service;
/**
* 稿
*
* 1. BX
* 2. DRAFT
* 3.
* 4.
*/
@Test
void insertShouldGenerateCodeAndFillSnapshots()
{
AmsRepairOrder order = buildRequest();
order.setReportUserId(999L);
order.setReportUserName("前端篡改报修人");
stubAsset("IN_STOCK");
when(sysCodeRuleService.nextCode("REPAIR_ORDER")).thenReturn("BX202606160001");
when(amsRepairOrderMapper.countOtherActiveRepairOrderByAssetId(eq(1L), eq(null), anyList()))
.thenReturn(0);
doAnswer(invocation -> {
AmsRepairOrder inserted = invocation.getArgument(0);
inserted.setRepairId(100L);
return 1;
}).when(amsRepairOrderMapper).insertAmsRepairOrder(any(AmsRepairOrder.class));
assertEquals(1, service.insertAmsRepairOrder(order,
operateContext(2L, "真实报修人", "admin")));
assertEquals("BX202606160001", order.getRepairNo());
assertEquals("DRAFT", order.getOrderStatus());
assertEquals(2L, order.getReportUserId());
assertEquals("真实报修人", order.getReportUserName());
assertNotNull(order.getReportTime());
assertEquals("admin", order.getCreateBy());
assertEquals("A001", order.getAssetCode());
assertEquals("一号资产", order.getAssetName());
assertEquals("CAT-001", order.getCategoryCode());
assertEquals("品牌A", order.getBrand());
assertEquals("型号X", order.getSpecModel());
assertEquals(BigDecimal.ZERO, order.getRepairCost());
assertNull(order.getRepairStartTime());
assertNull(order.getRepairFinishTime());
assertNull(order.getRejectReason());
}
/**
* 稿
* ServiceException
*/
@Test
void insertShouldRejectIfAssetOccupied()
{
AmsRepairOrder order = buildRequest();
stubAsset("IN_STOCK");
// 模拟资产已被其他报修单占用
when(amsRepairOrderMapper.countOtherActiveRepairOrderByAssetId(eq(1L), eq(null), anyList()))
.thenReturn(1);
ServiceException exception = assertThrows(ServiceException.class,
() -> service.insertAmsRepairOrder(order, operateContext(2L, "真实报修人", "admin")));
assertTrue(exception.getMessage().contains("已被其他未完结报修单占用"));
verify(amsRepairOrderMapper, never()).insertAmsRepairOrder(any(AmsRepairOrder.class));
}
/**
* 稿
* DRAFT DRAFT
*/
@Test
void updateShouldKeepDraftAndOverwriteSnapshots()
{
AmsRepairOrder order = buildPersistedOrder("DRAFT");
order.setFaultDesc("更新后的故障描述");
order.setRemark("更新后的备注");
order.setReportUserId(999L);
order.setReportUserName("前端篡改报修人");
AmsRepairOrder current = buildPersistedOrder("DRAFT");
when(amsRepairOrderMapper.selectAmsRepairOrderByRepairIdForUpdate(100L)).thenReturn(current);
stubAsset("IN_STOCK");
when(amsRepairOrderMapper.countOtherActiveRepairOrderByAssetId(eq(1L), eq(100L), anyList()))
.thenReturn(0);
when(amsRepairOrderMapper.updateAmsRepairOrder(any(AmsRepairOrder.class))).thenReturn(1);
assertEquals(1, service.updateAmsRepairOrder(order, adminContext()));
assertEquals("DRAFT", order.getOrderStatus());
assertEquals("admin", order.getUpdateBy());
assertEquals("DRAFT", order.getParams().get("expectedOrderStatus"));
assertEquals("更新后的故障描述", order.getFaultDesc());
assertEquals(2L, order.getReportUserId());
assertEquals("张三报修人", order.getReportUserName());
assertEquals("A001", order.getAssetCode()); // 强制回填覆盖
assertNull(order.getRepairStartTime());
assertNull(order.getRepairFinishTime());
assertEquals(BigDecimal.ZERO, order.getRepairCost());
}
/**
* DRAFT
* ServiceException
*/
@Test
void updateShouldRejectNonDraft()
{
AmsRepairOrder order = buildPersistedOrder("PENDING_CONFIRM");
AmsRepairOrder current = buildPersistedOrder("PENDING_CONFIRM");
when(amsRepairOrderMapper.selectAmsRepairOrderByRepairIdForUpdate(100L)).thenReturn(current);
ServiceException exception = assertThrows(ServiceException.class,
() -> service.updateAmsRepairOrder(order, adminContext()));
assertTrue(exception.getMessage().contains("仅草稿状态的报修单允许修改"));
verify(amsRepairOrderMapper, never()).updateAmsRepairOrder(any(AmsRepairOrder.class));
}
/**
*
* DRAFT PENDING_CONFIRM
*/
@Test
void submitShouldMoveDraftToPendingConfirm()
{
AmsRepairOrder order = buildPersistedOrder("DRAFT");
when(amsRepairOrderMapper.selectAmsRepairOrderByRepairIdForUpdate(100L)).thenReturn(order);
stubAsset("IN_STOCK");
when(amsRepairOrderMapper.countOtherActiveRepairOrderByAssetId(eq(1L), eq(100L), anyList()))
.thenReturn(0);
when(amsRepairOrderMapper.updateAmsRepairOrder(order)).thenReturn(1);
assertEquals(1, service.submitRepair(100L, adminContext()));
assertEquals("PENDING_CONFIRM", order.getOrderStatus());
assertEquals("DRAFT", order.getParams().get("expectedOrderStatus"));
verify(amsRepairOrderMapper).updateAmsRepairOrder(order);
}
/**
*
* PENDING_CONFIRM WAIT_REPAIR
*/
@Test
void confirmShouldMovePendingConfirmToWaitRepair()
{
AmsRepairOrder order = buildPersistedOrder("PENDING_CONFIRM");
order.setRejectReason("上次驳回的原因");
when(amsRepairOrderMapper.selectAmsRepairOrderByRepairIdForUpdate(100L)).thenReturn(order);
stubAsset("IN_STOCK");
when(amsRepairOrderMapper.countOtherActiveRepairOrderByAssetId(eq(1L), eq(100L), anyList()))
.thenReturn(0);
when(amsRepairOrderMapper.updateAmsRepairOrder(order)).thenReturn(1);
assertEquals(1, service.confirmRepair(100L, adminContext()));
assertEquals("WAIT_REPAIR", order.getOrderStatus());
assertEquals("PENDING_CONFIRM", order.getParams().get("expectedOrderStatus"));
assertNull(order.getRejectReason()); // 清空驳回记录
verify(amsRepairOrderMapper).updateAmsRepairOrder(order);
}
/**
* 0
* Service Controller 0
*/
@Test
void confirmShouldThrowIfUpdateRowsZero()
{
AmsRepairOrder order = buildPersistedOrder("PENDING_CONFIRM");
when(amsRepairOrderMapper.selectAmsRepairOrderByRepairIdForUpdate(100L)).thenReturn(order);
stubAsset("IN_STOCK");
when(amsRepairOrderMapper.countOtherActiveRepairOrderByAssetId(eq(1L), eq(100L), anyList()))
.thenReturn(0);
when(amsRepairOrderMapper.updateAmsRepairOrder(order)).thenReturn(0);
ServiceException exception = assertThrows(ServiceException.class,
() -> service.confirmRepair(100L, adminContext()));
assertTrue(exception.getMessage().contains("受理失败"));
}
/**
*
* PENDING_CONFIRM REJECTED
*/
@Test
void rejectShouldSetStatusAndReason()
{
AmsRepairOrder order = buildPersistedOrder("PENDING_CONFIRM");
when(amsRepairOrderMapper.selectAmsRepairOrderByRepairIdForUpdate(100L)).thenReturn(order);
when(amsRepairOrderMapper.updateAmsRepairOrder(order)).thenReturn(1);
assertEquals(1, service.rejectRepair(100L, "资产情况描述不清", adminContext()));
assertEquals("REJECTED", order.getOrderStatus());
assertEquals("PENDING_CONFIRM", order.getParams().get("expectedOrderStatus"));
assertEquals("资产情况描述不清", order.getRejectReason());
// 测试空驳回原因拦截
assertThrows(ServiceException.class, () -> service.rejectRepair(100L, "", adminContext()));
}
/**
* ()
*
* 1. WAIT_REPAIR REPAIRING
* 2. `startRepair`
* 3.
* 4. `beforeAssetStatus`
*/
@Test
void startRepairInternalShouldTransitionAssetAndSetStartTime()
{
AmsRepairOrder order = buildPersistedOrder("WAIT_REPAIR");
when(amsRepairOrderMapper.selectAmsRepairOrderByRepairIdForUpdate(100L)).thenReturn(order);
AmsAsset beforeAsset = stubAsset("IN_STOCK"); // 原本在库
when(assetStatusTransitionService.startRepair(eq(1L), any(AssetTransitionContext.class)))
.thenReturn(new AssetTransitionResult(beforeAsset, buildAsset("REPAIRING")));
when(amsRepairOrderMapper.updateAmsRepairOrder(order)).thenReturn(1);
AmsRepairOrder startParams = new AmsRepairOrder();
startParams.setRepairId(100L);
startParams.setRepairerType("INTERNAL");
startParams.setRepairUserId(5L);
startParams.setRepairUserName("张三");
startParams.setRepairContactPhone("13888888888");
assertEquals(1, service.startRepair(startParams, adminContext()));
assertEquals("REPAIRING", order.getOrderStatus());
assertEquals("INTERNAL", order.getRepairerType());
assertEquals(5L, order.getRepairUserId());
assertEquals("张三", order.getRepairUserName());
assertNull(order.getRepairOrgName());
assertEquals("13888888888", order.getRepairContactPhone());
assertEquals("IN_STOCK", order.getBeforeAssetStatus()); // 快照原本在库状态
assertEquals("WAIT_REPAIR", order.getParams().get("expectedOrderStatus"));
assertNotNull(order.getRepairStartTime());
ArgumentCaptor<AssetTransitionContext> contextCaptor = ArgumentCaptor.forClass(AssetTransitionContext.class);
verify(assetStatusTransitionService).startRepair(eq(1L), contextCaptor.capture());
assertEquals(100L, contextCaptor.getValue().getSourceOrderId());
assertEquals("BX202606160001", contextCaptor.getValue().getSourceOrderNo());
assertEquals(9L, contextCaptor.getValue().getOperateUserId());
assertEquals("管理员", contextCaptor.getValue().getOperateUserName());
}
/**
* ()
*
*/
@Test
void startRepairExternalShouldRequireOrgName()
{
AmsRepairOrder order = buildPersistedOrder("WAIT_REPAIR");
when(amsRepairOrderMapper.selectAmsRepairOrderByRepairIdForUpdate(100L)).thenReturn(order);
AmsAsset beforeAsset = stubAsset("IN_USE"); // 原本在用
when(assetStatusTransitionService.startRepair(eq(1L), any(AssetTransitionContext.class)))
.thenReturn(new AssetTransitionResult(beforeAsset, buildAsset("REPAIRING")));
when(amsRepairOrderMapper.updateAmsRepairOrder(order)).thenReturn(1);
AmsRepairOrder startParams = new AmsRepairOrder();
startParams.setRepairId(100L);
startParams.setRepairerType("EXTERNAL");
startParams.setRepairOrgName("外部维修公司");
startParams.setRepairUserName("李外部人");
startParams.setRepairContactPhone("13999999999");
assertEquals(1, service.startRepair(startParams, adminContext()));
assertEquals("REPAIRING", order.getOrderStatus());
assertEquals("EXTERNAL", order.getRepairerType());
assertNull(order.getRepairUserId());
assertEquals("李外部人", order.getRepairUserName());
assertEquals("外部维修公司", order.getRepairOrgName());
assertEquals("IN_USE", order.getBeforeAssetStatus()); // 快照原本在用状态
}
/**
*
*
* 1. REPAIR_DONE
* 2. `finishRepair` `beforeAssetStatus`
* 3.
*/
@Test
void finishRepairShouldCompleteOrderAndRestoreAssetStatus()
{
AmsRepairOrder order = buildPersistedOrder("REPAIRING");
// 模拟原本是在用状态
order.setBeforeAssetStatus("IN_USE");
Calendar cal = Calendar.getInstance();
cal.add(Calendar.HOUR_OF_DAY, -2);
order.setRepairStartTime(cal.getTime()); // 开始时间设为两小时前
when(amsRepairOrderMapper.selectAmsRepairOrderByRepairIdForUpdate(100L)).thenReturn(order);
AmsAsset beforeAsset = stubAsset("REPAIRING"); // 实物处于维修中
when(assetStatusTransitionService.finishRepair(eq(1L), eq("IN_USE"), any(AssetTransitionContext.class)))
.thenReturn(new AssetTransitionResult(beforeAsset, buildAsset("IN_USE")));
when(amsRepairOrderMapper.updateAmsRepairOrder(order)).thenReturn(1);
AmsRepairOrder finishParams = new AmsRepairOrder();
finishParams.setRepairId(100L);
finishParams.setRepairFinishTime(new Date());
finishParams.setRepairCost(new BigDecimal("150.00"));
finishParams.setRepairResult("更换了电源,目前通电测试正常");
assertEquals(1, service.finishRepair(finishParams, adminContext()));
assertEquals("REPAIR_DONE", order.getOrderStatus());
assertEquals("REPAIRING", order.getParams().get("expectedOrderStatus"));
assertEquals(new BigDecimal("150.00"), order.getRepairCost());
assertEquals("更换了电源,目前通电测试正常", order.getRepairResult());
assertNotNull(order.getRepairFinishTime());
ArgumentCaptor<AssetTransitionContext> contextCaptor = ArgumentCaptor.forClass(AssetTransitionContext.class);
// 验证流转服务被调用,且传入了维修前原状态 IN_USE
verify(assetStatusTransitionService).finishRepair(eq(1L), eq("IN_USE"), contextCaptor.capture());
assertEquals(100L, contextCaptor.getValue().getSourceOrderId());
assertEquals(9L, contextCaptor.getValue().getOperateUserId());
assertEquals("管理员", contextCaptor.getValue().getOperateUserName());
}
/**
*
*/
@Test
void finishRepairShouldRejectEarlyFinishTime()
{
AmsRepairOrder order = buildPersistedOrder("REPAIRING");
order.setRepairStartTime(new Date());
when(amsRepairOrderMapper.selectAmsRepairOrderByRepairIdForUpdate(100L)).thenReturn(order);
AmsRepairOrder finishParams = new AmsRepairOrder();
finishParams.setRepairId(100L);
// 完成时间设为昨天(早于开始时间)
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_YEAR, -1);
finishParams.setRepairFinishTime(cal.getTime());
finishParams.setRepairCost(new BigDecimal("50.00"));
finishParams.setRepairResult("测试");
assertThrows(ServiceException.class, () -> service.finishRepair(finishParams, adminContext()));
}
/**
* /稿
* DRAFT
*/
@Test
void deleteShouldOnlyAllowDraft()
{
// 1. 删除草稿单,验证成功
AmsRepairOrder order1 = buildPersistedOrder("DRAFT");
when(amsRepairOrderMapper.selectAmsRepairOrderByRepairIdForUpdate(100L)).thenReturn(order1);
when(amsRepairOrderMapper.deleteAmsRepairOrderByRepairId(100L)).thenReturn(1);
assertEquals(1, service.deleteAmsRepairOrderByRepairId(100L));
// 2. 删除非草稿单,验证抛出异常被拦截
AmsRepairOrder order2 = buildPersistedOrder("REPAIRING");
when(amsRepairOrderMapper.selectAmsRepairOrderByRepairIdForUpdate(200L)).thenReturn(order2);
assertThrows(ServiceException.class, () -> service.deleteAmsRepairOrderByRepairId(200L));
verify(amsRepairOrderMapper, never()).deleteAmsRepairOrderByRepairId(200L);
}
// ========================================== 辅助方法 ==========================================
private AmsRepairOrder buildRequest()
{
AmsRepairOrder order = new AmsRepairOrder();
order.setAssetId(1L);
order.setFaultDesc("资产电源无法开机");
order.setExpectedFinishTime(new Date());
order.setRemark("急需使用");
return order;
}
private AmsRepairOperateContext adminContext()
{
return operateContext(9L, "管理员", "admin");
}
private AmsRepairOperateContext operateContext(Long operateUserId, String operateUserName, String operateLoginName)
{
return new AmsRepairOperateContext(operateUserId, operateUserName, operateLoginName);
}
private AmsRepairOrder buildPersistedOrder(String status)
{
AmsRepairOrder order = buildRequest();
order.setRepairId(100L);
order.setRepairNo("BX202606160001");
order.setOrderStatus(status);
order.setRepairerType("INTERNAL");
order.setReportUserId(2L);
order.setReportUserName("张三报修人");
order.setReportTime(new Date());
order.setCreateBy("admin");
order.setCreateTime(new Date());
order.setDelFlag("0");
return order;
}
private AmsAsset stubAsset(String status)
{
AmsAsset asset = buildAsset(status);
when(amsAssetMapper.selectAmsAssetByAssetIdForUpdate(1L)).thenReturn(asset);
return asset;
}
private AmsAsset buildAsset(String status)
{
AmsAsset asset = new AmsAsset();
asset.setAssetId(1L);
asset.setAssetCode("A001");
asset.setAssetName("一号资产");
asset.setCategoryId(10L);
asset.setCategoryCode("CAT-001");
asset.setCategoryName("类别一");
asset.setBrand("品牌A");
asset.setSpecModel("型号X");
asset.setAssetStatus(status);
asset.setDelFlag("0");
return asset;
}
}
Loading…
Cancel
Save