feat(asset): 新增资产调拨明细报表功能

- 创建 AmsTransferDetailReport 实体类,定义调拨明细报表的数据结构
- 实现 AmsTransferDetailReportController 控制器,提供报表查询和导出接口
- 开发 AmsTransferDetailReportMapper 数据访问层,实现报表数据查询逻辑
- 配置 MyBatis 映射文件,定义报表查询的 SQL 语句和结果映射
- 实现 AmsTransferDetailReportService 业务逻辑层,处理报表数据检索
- 编写单元测试验证报表服务的正确性
- 创建 transfer.html 页面模板,实现报表的前端展示和筛选功能
main
yangk 3 days ago
parent 7ade861201
commit 238c5fb213

@ -0,0 +1,104 @@
package com.ruoyi.asset.controller;
import java.util.List;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.ruoyi.asset.domain.AmsAssetCategory;
import com.ruoyi.asset.domain.AmsTransferDetailReport;
import com.ruoyi.asset.domain.AmsWarehouse;
import com.ruoyi.asset.service.IAmsAssetCategoryService;
import com.ruoyi.asset.service.IAmsTransferDetailReportService;
import com.ruoyi.asset.service.IAmsWarehouseService;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.poi.ExcelUtil;
/**
* Controller
*
* @author Yangk
*/
@Controller
@RequestMapping("/asset/report/transfer")
public class AmsTransferDetailReportController extends BaseController
{
private static final String ENABLED_YES = "Y";
private String prefix = "asset/report";
@Autowired
private IAmsTransferDetailReportService amsTransferDetailReportService;
@Autowired
private IAmsAssetCategoryService amsAssetCategoryService;
@Autowired
private IAmsWarehouseService amsWarehouseService;
@RequiresPermissions("asset:report:transfer:view")
@GetMapping()
public String transfer(ModelMap mmap)
{
putReportOptions(mmap);
return prefix + "/transfer";
}
/**
*
*/
@RequiresPermissions("asset:report:transfer:list")
@PostMapping("/list")
@ResponseBody
public TableDataInfo list(AmsTransferDetailReport amsTransferDetailReport)
{
startPage();
List<AmsTransferDetailReport> list = amsTransferDetailReportService
.selectTransferDetailReportList(amsTransferDetailReport);
return getDataTable(list);
}
/**
*
*/
@RequiresPermissions("asset:report:transfer:export")
@Log(title = "调拨明细报表", businessType = BusinessType.EXPORT)
@PostMapping("/export")
@ResponseBody
public AjaxResult export(AmsTransferDetailReport amsTransferDetailReport)
{
List<AmsTransferDetailReport> list = amsTransferDetailReportService
.selectTransferDetailReportList(amsTransferDetailReport);
ExcelUtil<AmsTransferDetailReport> util = new ExcelUtil<AmsTransferDetailReport>(
AmsTransferDetailReport.class);
return util.exportExcel(list, "调拨明细报表数据");
}
private void putReportOptions(ModelMap mmap)
{
mmap.put("warehouseList", selectEnabledWarehouseList());
mmap.put("categoryList", selectEnabledCategoryList());
}
private List<AmsWarehouse> selectEnabledWarehouseList()
{
AmsWarehouse warehouse = new AmsWarehouse();
warehouse.setEnabled(ENABLED_YES);
return amsWarehouseService.selectAmsWarehouseList(warehouse);
}
private List<AmsAssetCategory> selectEnabledCategoryList()
{
AmsAssetCategory category = new AmsAssetCategory();
category.setEnabled(ENABLED_YES);
return amsAssetCategoryService.selectAmsAssetCategoryList(category);
}
}

@ -0,0 +1,293 @@
package com.ruoyi.asset.domain;
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;
/**
*
*
* @author Yangk
*/
public class AmsTransferDetailReport extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 调拨单号 */
@Excel(name = "调拨单号")
private String transferNo;
/** 调拨确认时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Excel(name = "调拨日期", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date confirmTime;
/** 申请人 */
@Excel(name = "申请人")
private String applicantName;
/** 确认人 */
@Excel(name = "确认人")
private String confirmUserName;
/** 调拨原因 */
@Excel(name = "调拨原因")
private String transferReason;
/** 资产编码 */
@Excel(name = "资产编码")
private String assetCode;
/** 资产名称 */
@Excel(name = "资产名称")
private String assetName;
/** 资产类别ID */
private Long categoryId;
/** 资产类别 */
@Excel(name = "资产类别")
private String categoryName;
/** 规格型号 */
@Excel(name = "规格型号")
private String specModel;
/** 品牌 */
@Excel(name = "品牌")
private String brand;
/** 调拨前仓库ID */
private Long oldWarehouseId;
/** 调拨前仓库 */
@Excel(name = "调拨前仓库")
private String oldWarehouseName;
/** 调拨前位置 */
@Excel(name = "调拨前位置")
private String oldLocationName;
/** 调拨后仓库ID */
private Long newWarehouseId;
/** 调拨后仓库 */
@Excel(name = "调拨后仓库")
private String newWarehouseName;
/** 调拨后位置 */
@Excel(name = "调拨后位置")
private String newLocationName;
/** 明细备注 */
@Excel(name = "备注")
private String remark;
public void setTransferNo(String transferNo)
{
this.transferNo = transferNo;
}
public String getTransferNo()
{
return transferNo;
}
public void setConfirmTime(Date confirmTime)
{
this.confirmTime = confirmTime;
}
public Date getConfirmTime()
{
return confirmTime;
}
public void setApplicantName(String applicantName)
{
this.applicantName = applicantName;
}
public String getApplicantName()
{
return applicantName;
}
public void setConfirmUserName(String confirmUserName)
{
this.confirmUserName = confirmUserName;
}
public String getConfirmUserName()
{
return confirmUserName;
}
public void setTransferReason(String transferReason)
{
this.transferReason = transferReason;
}
public String getTransferReason()
{
return transferReason;
}
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 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 setOldWarehouseId(Long oldWarehouseId)
{
this.oldWarehouseId = oldWarehouseId;
}
public Long getOldWarehouseId()
{
return oldWarehouseId;
}
public void setOldWarehouseName(String oldWarehouseName)
{
this.oldWarehouseName = oldWarehouseName;
}
public String getOldWarehouseName()
{
return oldWarehouseName;
}
public void setOldLocationName(String oldLocationName)
{
this.oldLocationName = oldLocationName;
}
public String getOldLocationName()
{
return oldLocationName;
}
public void setNewWarehouseId(Long newWarehouseId)
{
this.newWarehouseId = newWarehouseId;
}
public Long getNewWarehouseId()
{
return newWarehouseId;
}
public void setNewWarehouseName(String newWarehouseName)
{
this.newWarehouseName = newWarehouseName;
}
public String getNewWarehouseName()
{
return newWarehouseName;
}
public void setNewLocationName(String newLocationName)
{
this.newLocationName = newLocationName;
}
public String getNewLocationName()
{
return newLocationName;
}
public void setRemark(String remark)
{
this.remark = remark;
}
public String getRemark()
{
return remark;
}
@Override
public String toString()
{
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("transferNo", getTransferNo())
.append("confirmTime", getConfirmTime())
.append("applicantName", getApplicantName())
.append("confirmUserName", getConfirmUserName())
.append("transferReason", getTransferReason())
.append("assetCode", getAssetCode())
.append("assetName", getAssetName())
.append("categoryId", getCategoryId())
.append("categoryName", getCategoryName())
.append("specModel", getSpecModel())
.append("brand", getBrand())
.append("oldWarehouseId", getOldWarehouseId())
.append("oldWarehouseName", getOldWarehouseName())
.append("oldLocationName", getOldLocationName())
.append("newWarehouseId", getNewWarehouseId())
.append("newWarehouseName", getNewWarehouseName())
.append("newLocationName", getNewLocationName())
.append("remark", getRemark())
.toString();
}
}

@ -0,0 +1,23 @@
package com.ruoyi.asset.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.ruoyi.asset.domain.AmsTransferDetailReport;
/**
* Mapper
*
* @author Yangk
*/
public interface AmsTransferDetailReportMapper
{
/**
*
*
* @param report
* @param transferredStatus
* @return
*/
List<AmsTransferDetailReport> selectTransferDetailReportList(@Param("report") AmsTransferDetailReport report,
@Param("transferredStatus") String transferredStatus);
}

@ -0,0 +1,21 @@
package com.ruoyi.asset.service;
import java.util.List;
import com.ruoyi.asset.domain.AmsTransferDetailReport;
/**
* Service
*
* @author Yangk
*/
public interface IAmsTransferDetailReportService
{
/**
*
*
* @param amsTransferDetailReport
* @return
*/
List<AmsTransferDetailReport> selectTransferDetailReportList(
AmsTransferDetailReport amsTransferDetailReport);
}

@ -0,0 +1,35 @@
package com.ruoyi.asset.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.asset.constant.TransferOrderStatus;
import com.ruoyi.asset.domain.AmsTransferDetailReport;
import com.ruoyi.asset.mapper.AmsTransferDetailReportMapper;
import com.ruoyi.asset.service.IAmsTransferDetailReportService;
/**
* Service
*
* @author Yangk
*/
@Service
public class AmsTransferDetailReportServiceImpl implements IAmsTransferDetailReportService
{
@Autowired
private AmsTransferDetailReportMapper amsTransferDetailReportMapper;
/**
*
*/
@Override
public List<AmsTransferDetailReport> selectTransferDetailReportList(
AmsTransferDetailReport amsTransferDetailReport)
{
AmsTransferDetailReport query = amsTransferDetailReport == null ? new AmsTransferDetailReport()
: amsTransferDetailReport;
// 报表只统计已确认完成的调拨事实,状态由服务端固定,避免前端把草稿或待确认单混入统计。
return amsTransferDetailReportMapper.selectTransferDetailReportList(query,
TransferOrderStatus.TRANSFERRED);
}
}

@ -0,0 +1,89 @@
<?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.AmsTransferDetailReportMapper">
<resultMap type="AmsTransferDetailReport" id="AmsTransferDetailReportResult">
<result property="transferNo" column="transfer_no"/>
<result property="confirmTime" column="confirm_time"/>
<result property="applicantName" column="applicant_name"/>
<result property="confirmUserName" column="confirm_user_name"/>
<result property="transferReason" column="transfer_reason"/>
<result property="assetCode" column="asset_code"/>
<result property="assetName" column="asset_name"/>
<result property="categoryId" column="category_id"/>
<result property="categoryName" column="category_name"/>
<result property="specModel" column="spec_model"/>
<result property="brand" column="brand"/>
<result property="oldWarehouseId" column="old_warehouse_id"/>
<result property="oldWarehouseName" column="old_warehouse_name"/>
<result property="oldLocationName" column="old_location_name"/>
<result property="newWarehouseId" column="new_warehouse_id"/>
<result property="newWarehouseName" column="new_warehouse_name"/>
<result property="newLocationName" column="new_location_name"/>
<result property="remark" column="remark"/>
</resultMap>
<sql id="transferDetailReportColumns">
o.transfer_no,
o.confirm_time,
o.applicant_name,
o.confirm_user_name,
o.transfer_reason,
i.asset_code,
i.asset_name,
i.category_id,
i.category_name,
i.spec_model,
i.brand,
i.old_warehouse_id,
i.old_warehouse_name,
i.old_location_name,
i.new_warehouse_id,
i.new_warehouse_name,
i.new_location_name,
i.remark
</sql>
<sql id="transferDetailReportWhere">
<where>
o.del_flag = '0'
and i.del_flag = '0'
and o.order_status = #{transferredStatus}
<if test="report != null and report.transferNo != null and report.transferNo != ''">
and o.transfer_no like concat(#{report.transferNo}, '%')
</if>
<if test="report != null and report.applicantName != null and report.applicantName != ''">
and o.applicant_name like concat('%', #{report.applicantName}, '%')
</if>
<if test="report != null and report.categoryId != null">
and i.category_id = #{report.categoryId}
</if>
<if test="report != null and report.assetCode != null and report.assetCode != ''">
and i.asset_code like concat(#{report.assetCode}, '%')
</if>
<if test="report != null and report.oldWarehouseId != null">
and i.old_warehouse_id = #{report.oldWarehouseId}
</if>
<if test="report != null and report.newWarehouseId != null">
and i.new_warehouse_id = #{report.newWarehouseId}
</if>
<if test="report != null and report.params.beginConfirmTime != null and report.params.beginConfirmTime != ''">
and o.confirm_time &gt;= #{report.params.beginConfirmTime}
</if>
<if test="report != null and report.params.endConfirmTime != null and report.params.endConfirmTime != ''">
and o.confirm_time &lt; date_add(#{report.params.endConfirmTime}, interval 1 day)
</if>
</where>
</sql>
<select id="selectTransferDetailReportList" resultMap="AmsTransferDetailReportResult">
select
<include refid="transferDetailReportColumns"/>
from ams_transfer_order o
inner join ams_transfer_order_item i on i.order_id = o.order_id
<include refid="transferDetailReportWhere"/>
order by o.confirm_time desc, o.order_id desc, i.item_id asc
</select>
</mapper>

@ -0,0 +1,151 @@
<!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('调拨明细报表')" />
<th:block th:include="include :: datetimepicker-css" />
<th:block th:include="include :: select2-css" />
</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="transferNo"/>
</li>
<li class="select-time">
<label>调拨日期:</label>
<input type="text" class="time-input" id="startTime" placeholder="开始时间"
name="params[beginConfirmTime]"/>
<span>-</span>
<input type="text" class="time-input" id="endTime" placeholder="结束时间"
name="params[endConfirmTime]"/>
</li>
<li>
<label>申请人:</label>
<input type="text" name="applicantName"/>
</li>
<li>
<label>资产编码:</label>
<input type="text" name="assetCode"/>
</li>
<li>
<label>资产类别:</label>
<select name="categoryId" class="select2-control" style="width: 170px">
<option value="">所有</option>
<option th:each="category : ${categoryList}"
th:value="${category.categoryId}"
th:text="${category.categoryName}"></option>
</select>
</li>
<li>
<label>原仓库:</label>
<select name="oldWarehouseId" class="select2-control" style="width: 170px">
<option value="">所有</option>
<option th:each="warehouse : ${warehouseList}"
th:value="${warehouse.warehouseId}"
th:text="${warehouse.warehouseCode + ' - ' + warehouse.warehouseName}"></option>
</select>
</li>
<li>
<label>新仓库:</label>
<select name="newWarehouseId" class="select2-control" style="width: 170px">
<option value="">所有</option>
<option th:each="warehouse : ${warehouseList}"
th:value="${warehouse.warehouseId}"
th:text="${warehouse.warehouseCode + ' - ' + warehouse.warehouseName}"></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="resetReportSearch()">
<i class="fa fa-refresh"></i>&nbsp;重置
</a>
</li>
</ul>
</div>
</form>
</div>
<div class="btn-group-sm" id="toolbar" role="group">
<a class="btn btn-warning" onclick="$.table.exportExcel()" shiro:hasPermission="asset:report:transfer: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" />
<th:block th:include="include :: datetimepicker-js" />
<th:block th:include="include :: select2-js" />
<script th:inline="javascript">
var prefix = ctx + "asset/report/transfer";
$(function() {
$(".select2-control").select2({
placeholder: "请选择",
allowClear: true
});
var options = {
url: prefix + "/list",
exportUrl: prefix + "/export",
modalName: "调拨明细报表",
columns: [{
field: 'transferNo',
title: '调拨单号'
},
{
field: 'confirmTime',
title: '调拨日期'
},
{
field: 'applicantName',
title: '申请人'
},
{
field: 'assetCode',
title: '资产编码'
},
{
field: 'assetName',
title: '资产名称'
},
{
field: 'categoryName',
title: '资产类别'
},
{
field: 'oldWarehouseName',
title: '调拨前仓库'
},
{
field: 'oldLocationName',
title: '调拨前位置'
},
{
field: 'newWarehouseName',
title: '调拨后仓库'
},
{
field: 'newLocationName',
title: '调拨后位置'
}]
};
$.table.init(options);
});
function resetReportSearch() {
$.form.reset();
$(".select2-control").val("").trigger("change");
$.table.search();
}
</script>
</body>
</html>

@ -0,0 +1,87 @@
package com.ruoyi.asset.service.impl;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.List;
import com.ruoyi.asset.constant.TransferOrderStatus;
import com.ruoyi.asset.domain.AmsTransferDetailReport;
import com.ruoyi.asset.mapper.AmsTransferDetailReportMapper;
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;
@ExtendWith(MockitoExtension.class)
class AmsTransferDetailReportServiceImplTest
{
@Mock
private AmsTransferDetailReportMapper amsTransferDetailReportMapper;
@InjectMocks
private AmsTransferDetailReportServiceImpl service;
/** 报表只能看已确认调拨,防止草稿和待确认单据影响正式统计口径。 */
@Test
void selectTransferDetailReportListShouldAlwaysUseTransferredStatus()
{
AmsTransferDetailReport query = new AmsTransferDetailReport();
query.setTransferNo("DB202606");
query.setApplicantName("若依");
query.setAssetCode("ASSET-001");
query.setCategoryId(1L);
query.setOldWarehouseId(10L);
query.setNewWarehouseId(20L);
when(amsTransferDetailReportMapper.selectTransferDetailReportList(query,
TransferOrderStatus.TRANSFERRED)).thenReturn(List.of());
service.selectTransferDetailReportList(query);
ArgumentCaptor<AmsTransferDetailReport> reportCaptor = ArgumentCaptor
.forClass(AmsTransferDetailReport.class);
verify(amsTransferDetailReportMapper).selectTransferDetailReportList(reportCaptor.capture(),
eq(TransferOrderStatus.TRANSFERRED));
assertEquals("DB202606", reportCaptor.getValue().getTransferNo());
assertEquals("若依", reportCaptor.getValue().getApplicantName());
assertEquals("ASSET-001", reportCaptor.getValue().getAssetCode());
assertEquals(1L, reportCaptor.getValue().getCategoryId());
assertEquals(10L, reportCaptor.getValue().getOldWarehouseId());
assertEquals(20L, reportCaptor.getValue().getNewWarehouseId());
}
@Test
void selectTransferDetailReportListShouldHandleNullQuery()
{
when(amsTransferDetailReportMapper.selectTransferDetailReportList(any(AmsTransferDetailReport.class),
eq(TransferOrderStatus.TRANSFERRED))).thenReturn(List.of());
service.selectTransferDetailReportList(null);
verify(amsTransferDetailReportMapper).selectTransferDetailReportList(any(AmsTransferDetailReport.class),
eq(TransferOrderStatus.TRANSFERRED));
}
@Test
void selectTransferDetailReportListShouldReturnMapperRows()
{
AmsTransferDetailReport row = new AmsTransferDetailReport();
row.setTransferNo("DB202606120001");
row.setAssetCode("ASSET-001");
row.setNewWarehouseName("二号仓");
when(amsTransferDetailReportMapper.selectTransferDetailReportList(any(AmsTransferDetailReport.class),
eq(TransferOrderStatus.TRANSFERRED))).thenReturn(List.of(row));
List<AmsTransferDetailReport> result = service
.selectTransferDetailReportList(new AmsTransferDetailReport());
assertEquals(1, result.size());
assertEquals("DB202606120001", result.get(0).getTransferNo());
assertEquals("ASSET-001", result.get(0).getAssetCode());
assertEquals("二号仓", result.get(0).getNewWarehouseName());
}
}
Loading…
Cancel
Save