From 6eed283f72b5aaf504f04eb465e7146a64ed0042 Mon Sep 17 00:00:00 2001 From: yangk Date: Tue, 30 Jun 2026 13:39:25 +0800 Subject: [PATCH] =?UTF-8?q?feat(asset):=20=E6=96=B0=E5=A2=9E=E5=80=9F?= =?UTF-8?q?=E7=94=A8=E5=BD=92=E8=BF=98=E6=98=8E=E7=BB=86=E6=8A=A5=E8=A1=A8?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 创建 AmsBorrowReturnDetailReport 实体类,定义报表所需字段 - 实现 AmsBorrowReturnDetailReportController 控制器,提供报表查询和导出接口 - 开发 AmsBorrowReturnDetailReportMapper 数据访问层,编写报表查询SQL - 实现 AmsBorrowReturnDetailReportService 业务逻辑层,处理报表数据查询 - 添加报表页面模板 borrow.html,实现前端展示和筛选功能 - 编写单元测试验证报表查询逻辑的正确性 - 集成权限控制和Excel导出功能 --- ...AmsBorrowReturnDetailReportController.java | 86 ++++++ .../domain/AmsBorrowReturnDetailReport.java | 282 ++++++++++++++++++ .../AmsBorrowReturnDetailReportMapper.java | 24 ++ .../IAmsBorrowReturnDetailReportService.java | 21 ++ ...msBorrowReturnDetailReportServiceImpl.java | 40 +++ .../AmsBorrowReturnDetailReportMapper.xml | 87 ++++++ .../templates/asset/report/borrow.html | 147 +++++++++ ...rrowReturnDetailReportServiceImplTest.java | 82 +++++ 8 files changed, 769 insertions(+) create mode 100644 ruoyi-asset/src/main/java/com/ruoyi/asset/controller/AmsBorrowReturnDetailReportController.java create mode 100644 ruoyi-asset/src/main/java/com/ruoyi/asset/domain/AmsBorrowReturnDetailReport.java create mode 100644 ruoyi-asset/src/main/java/com/ruoyi/asset/mapper/AmsBorrowReturnDetailReportMapper.java create mode 100644 ruoyi-asset/src/main/java/com/ruoyi/asset/service/IAmsBorrowReturnDetailReportService.java create mode 100644 ruoyi-asset/src/main/java/com/ruoyi/asset/service/impl/AmsBorrowReturnDetailReportServiceImpl.java create mode 100644 ruoyi-asset/src/main/resources/mapper/asset/AmsBorrowReturnDetailReportMapper.xml create mode 100644 ruoyi-asset/src/main/resources/templates/asset/report/borrow.html create mode 100644 ruoyi-asset/src/test/java/com/ruoyi/asset/service/impl/AmsBorrowReturnDetailReportServiceImplTest.java diff --git a/ruoyi-asset/src/main/java/com/ruoyi/asset/controller/AmsBorrowReturnDetailReportController.java b/ruoyi-asset/src/main/java/com/ruoyi/asset/controller/AmsBorrowReturnDetailReportController.java new file mode 100644 index 0000000..b46a457 --- /dev/null +++ b/ruoyi-asset/src/main/java/com/ruoyi/asset/controller/AmsBorrowReturnDetailReportController.java @@ -0,0 +1,86 @@ +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.AmsBorrowReturnDetailReport; +import com.ruoyi.asset.service.IAmsAssetCategoryService; +import com.ruoyi.asset.service.IAmsBorrowReturnDetailReportService; +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/borrow") +public class AmsBorrowReturnDetailReportController extends BaseController +{ + private static final String ENABLED_YES = "Y"; + + private String prefix = "asset/report"; + + @Autowired + private IAmsBorrowReturnDetailReportService amsBorrowReturnDetailReportService; + + @Autowired + private IAmsAssetCategoryService amsAssetCategoryService; + + @RequiresPermissions("asset:report:borrow:view") + @GetMapping() + public String borrow(ModelMap mmap) + { + mmap.put("categoryList", selectEnabledCategoryList()); + return prefix + "/borrow"; + } + + /** + * 查询借用归还明细报表列表。 + */ + @RequiresPermissions("asset:report:borrow:list") + @PostMapping("/list") + @ResponseBody + public TableDataInfo list(AmsBorrowReturnDetailReport amsBorrowReturnDetailReport) + { + startPage(); + List list = amsBorrowReturnDetailReportService + .selectBorrowReturnDetailReportList(amsBorrowReturnDetailReport); + return getDataTable(list); + } + + /** + * 导出借用归还明细报表。 + */ + @RequiresPermissions("asset:report:borrow:export") + @Log(title = "借用归还明细报表", businessType = BusinessType.EXPORT) + @PostMapping("/export") + @ResponseBody + public AjaxResult export(AmsBorrowReturnDetailReport amsBorrowReturnDetailReport) + { + List list = amsBorrowReturnDetailReportService + .selectBorrowReturnDetailReportList(amsBorrowReturnDetailReport); + ExcelUtil util = new ExcelUtil( + AmsBorrowReturnDetailReport.class); + return util.exportExcel(list, "借用归还明细报表数据"); + } + + private List selectEnabledCategoryList() + { + AmsAssetCategory category = new AmsAssetCategory(); + category.setEnabled(ENABLED_YES); + return amsAssetCategoryService.selectAmsAssetCategoryList(category); + } +} diff --git a/ruoyi-asset/src/main/java/com/ruoyi/asset/domain/AmsBorrowReturnDetailReport.java b/ruoyi-asset/src/main/java/com/ruoyi/asset/domain/AmsBorrowReturnDetailReport.java new file mode 100644 index 0000000..3432a4e --- /dev/null +++ b/ruoyi-asset/src/main/java/com/ruoyi/asset/domain/AmsBorrowReturnDetailReport.java @@ -0,0 +1,282 @@ +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 AmsBorrowReturnDetailReport extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 借用单号 */ + @Excel(name = "借用单号") + private String borrowNo; + + /** 借用确认时间 */ + @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 borrowUserName; + + /** 借用部门 */ + @Excel(name = "借用部门") + private String borrowDeptName; + + /** 预计归还日期 */ + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") + @Excel(name = "预计归还日期", width = 30, dateFormat = "yyyy-MM-dd") + private Date expectedReturnDate; + + /** 实际归还日期 */ + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") + @Excel(name = "实际归还日期", width = 30, dateFormat = "yyyy-MM-dd") + private Date actualReturnDate; + + /** 资产编码 */ + @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; + + /** 借用前仓库 */ + @Excel(name = "借用前仓库") + private String beforeWarehouseName; + + /** 借用前位置 */ + @Excel(name = "借用前位置") + private String beforeLocationName; + + /** 归还仓库 */ + @Excel(name = "归还仓库") + private String returnWarehouseName; + + /** 归还位置 */ + @Excel(name = "归还位置") + private String returnLocationName; + + /** 归还状态 */ + @Excel(name = "归还状态", dictType = "ams_borrow_status") + private String returnStatus; + + public void setBorrowNo(String borrowNo) + { + this.borrowNo = borrowNo; + } + + public String getBorrowNo() + { + return borrowNo; + } + + public void setConfirmTime(Date confirmTime) + { + this.confirmTime = confirmTime; + } + + public Date getConfirmTime() + { + return confirmTime; + } + + public void setBorrowUserName(String borrowUserName) + { + this.borrowUserName = borrowUserName; + } + + public String getBorrowUserName() + { + return borrowUserName; + } + + public void setBorrowDeptName(String borrowDeptName) + { + this.borrowDeptName = borrowDeptName; + } + + public String getBorrowDeptName() + { + return borrowDeptName; + } + + public void setExpectedReturnDate(Date expectedReturnDate) + { + this.expectedReturnDate = expectedReturnDate; + } + + public Date getExpectedReturnDate() + { + return expectedReturnDate; + } + + public void setActualReturnDate(Date actualReturnDate) + { + this.actualReturnDate = actualReturnDate; + } + + public Date getActualReturnDate() + { + return actualReturnDate; + } + + 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 setBeforeWarehouseName(String beforeWarehouseName) + { + this.beforeWarehouseName = beforeWarehouseName; + } + + public String getBeforeWarehouseName() + { + return beforeWarehouseName; + } + + public void setBeforeLocationName(String beforeLocationName) + { + this.beforeLocationName = beforeLocationName; + } + + public String getBeforeLocationName() + { + return beforeLocationName; + } + + public void setReturnWarehouseName(String returnWarehouseName) + { + this.returnWarehouseName = returnWarehouseName; + } + + public String getReturnWarehouseName() + { + return returnWarehouseName; + } + + public void setReturnLocationName(String returnLocationName) + { + this.returnLocationName = returnLocationName; + } + + public String getReturnLocationName() + { + return returnLocationName; + } + + public void setReturnStatus(String returnStatus) + { + this.returnStatus = returnStatus; + } + + public String getReturnStatus() + { + return returnStatus; + } + + @Override + public String toString() + { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("borrowNo", getBorrowNo()) + .append("confirmTime", getConfirmTime()) + .append("borrowUserName", getBorrowUserName()) + .append("borrowDeptName", getBorrowDeptName()) + .append("expectedReturnDate", getExpectedReturnDate()) + .append("actualReturnDate", getActualReturnDate()) + .append("assetCode", getAssetCode()) + .append("assetName", getAssetName()) + .append("categoryId", getCategoryId()) + .append("categoryName", getCategoryName()) + .append("specModel", getSpecModel()) + .append("brand", getBrand()) + .append("beforeWarehouseName", getBeforeWarehouseName()) + .append("beforeLocationName", getBeforeLocationName()) + .append("returnWarehouseName", getReturnWarehouseName()) + .append("returnLocationName", getReturnLocationName()) + .append("returnStatus", getReturnStatus()) + .toString(); + } +} diff --git a/ruoyi-asset/src/main/java/com/ruoyi/asset/mapper/AmsBorrowReturnDetailReportMapper.java b/ruoyi-asset/src/main/java/com/ruoyi/asset/mapper/AmsBorrowReturnDetailReportMapper.java new file mode 100644 index 0000000..cef7dc7 --- /dev/null +++ b/ruoyi-asset/src/main/java/com/ruoyi/asset/mapper/AmsBorrowReturnDetailReportMapper.java @@ -0,0 +1,24 @@ +package com.ruoyi.asset.mapper; + +import java.util.List; +import com.ruoyi.asset.domain.AmsBorrowReturnDetailReport; +import org.apache.ibatis.annotations.Param; + +/** + * 借用归还明细报表Mapper接口 + * + * @author Yangk + */ +public interface AmsBorrowReturnDetailReportMapper +{ + /** + * 查询借用归还明细报表列表。 + * + * @param report 查询条件 + * @param effectiveStatuses 已产生正式借用事实的主单状态 + * @return 借用归还明细报表集合 + */ + public List selectBorrowReturnDetailReportList( + @Param("report") AmsBorrowReturnDetailReport report, + @Param("effectiveStatuses") List effectiveStatuses); +} diff --git a/ruoyi-asset/src/main/java/com/ruoyi/asset/service/IAmsBorrowReturnDetailReportService.java b/ruoyi-asset/src/main/java/com/ruoyi/asset/service/IAmsBorrowReturnDetailReportService.java new file mode 100644 index 0000000..37cb5b7 --- /dev/null +++ b/ruoyi-asset/src/main/java/com/ruoyi/asset/service/IAmsBorrowReturnDetailReportService.java @@ -0,0 +1,21 @@ +package com.ruoyi.asset.service; + +import java.util.List; +import com.ruoyi.asset.domain.AmsBorrowReturnDetailReport; + +/** + * 借用归还明细报表Service接口 + * + * @author Yangk + */ +public interface IAmsBorrowReturnDetailReportService +{ + /** + * 查询借用归还明细报表列表。 + * + * @param amsBorrowReturnDetailReport 借用归还明细报表查询条件 + * @return 借用归还明细报表集合 + */ + public List selectBorrowReturnDetailReportList( + AmsBorrowReturnDetailReport amsBorrowReturnDetailReport); +} diff --git a/ruoyi-asset/src/main/java/com/ruoyi/asset/service/impl/AmsBorrowReturnDetailReportServiceImpl.java b/ruoyi-asset/src/main/java/com/ruoyi/asset/service/impl/AmsBorrowReturnDetailReportServiceImpl.java new file mode 100644 index 0000000..52b62b2 --- /dev/null +++ b/ruoyi-asset/src/main/java/com/ruoyi/asset/service/impl/AmsBorrowReturnDetailReportServiceImpl.java @@ -0,0 +1,40 @@ +package com.ruoyi.asset.service.impl; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.asset.constant.BorrowOrderStatus; +import com.ruoyi.asset.domain.AmsBorrowReturnDetailReport; +import com.ruoyi.asset.mapper.AmsBorrowReturnDetailReportMapper; +import com.ruoyi.asset.service.IAmsBorrowReturnDetailReportService; + +/** + * 借用归还明细报表Service业务层处理 + * + * @author Yangk + */ +@Service +public class AmsBorrowReturnDetailReportServiceImpl implements IAmsBorrowReturnDetailReportService +{ + private static final List EFFECTIVE_BORROW_STATUSES = Collections.unmodifiableList(Arrays.asList( + BorrowOrderStatus.BORROWING, BorrowOrderStatus.PENDING_RETURN_CONFIRM, + BorrowOrderStatus.BORROW_RETURNED)); + + @Autowired + private AmsBorrowReturnDetailReportMapper amsBorrowReturnDetailReportMapper; + + /** + * 查询借用归还明细报表列表。 + */ + @Override + public List selectBorrowReturnDetailReportList( + AmsBorrowReturnDetailReport amsBorrowReturnDetailReport) + { + AmsBorrowReturnDetailReport query = amsBorrowReturnDetailReport == null ? new AmsBorrowReturnDetailReport() + : amsBorrowReturnDetailReport; + // 报表只统计已形成借用事实的单据,状态口径固定在服务端,避免前端把草稿或驳回单带入统计。 + return amsBorrowReturnDetailReportMapper.selectBorrowReturnDetailReportList(query, EFFECTIVE_BORROW_STATUSES); + } +} diff --git a/ruoyi-asset/src/main/resources/mapper/asset/AmsBorrowReturnDetailReportMapper.xml b/ruoyi-asset/src/main/resources/mapper/asset/AmsBorrowReturnDetailReportMapper.xml new file mode 100644 index 0000000..248e1f1 --- /dev/null +++ b/ruoyi-asset/src/main/resources/mapper/asset/AmsBorrowReturnDetailReportMapper.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + o.borrow_no, + o.confirm_time, + o.borrow_user_name, + o.borrow_dept_name, + o.expected_return_date, + i.actual_return_date, + i.asset_code, + i.asset_name, + i.category_id, + i.category_name, + i.spec_model, + i.brand, + i.before_warehouse_name, + i.before_location_name, + i.return_warehouse_name, + i.return_location_name, + i.return_status + + + + + o.del_flag = '0' + and i.del_flag = '0' + and o.order_status in + + #{status} + + + and o.borrow_no like concat(#{report.borrowNo}, '%') + + + and o.borrow_user_name like concat('%', #{report.borrowUserName}, '%') + + + and i.category_id = #{report.categoryId} + + + and i.asset_code like concat(#{report.assetCode}, '%') + + + and i.return_status = #{report.returnStatus} + + + and o.confirm_time >= #{report.params.beginConfirmTime} + + + and o.confirm_time < date_add(#{report.params.endConfirmTime}, interval 1 day) + + + + + + diff --git a/ruoyi-asset/src/main/resources/templates/asset/report/borrow.html b/ruoyi-asset/src/main/resources/templates/asset/report/borrow.html new file mode 100644 index 0000000..ea9e1f6 --- /dev/null +++ b/ruoyi-asset/src/main/resources/templates/asset/report/borrow.html @@ -0,0 +1,147 @@ + + + + + + + + +
+
+
+
+
+
    +
  • + + +
  • +
  • + + + - + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + +  搜索 + + +  重置 + +
  • +
+
+
+
+ + +
+
+
+
+
+ + + + + + diff --git a/ruoyi-asset/src/test/java/com/ruoyi/asset/service/impl/AmsBorrowReturnDetailReportServiceImplTest.java b/ruoyi-asset/src/test/java/com/ruoyi/asset/service/impl/AmsBorrowReturnDetailReportServiceImplTest.java new file mode 100644 index 0000000..c4a6885 --- /dev/null +++ b/ruoyi-asset/src/test/java/com/ruoyi/asset/service/impl/AmsBorrowReturnDetailReportServiceImplTest.java @@ -0,0 +1,82 @@ +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.anyList; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.List; +import com.ruoyi.asset.constant.BorrowOrderStatus; +import com.ruoyi.asset.domain.AmsBorrowReturnDetailReport; +import com.ruoyi.asset.mapper.AmsBorrowReturnDetailReportMapper; +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 AmsBorrowReturnDetailReportServiceImplTest +{ + @Mock + private AmsBorrowReturnDetailReportMapper amsBorrowReturnDetailReportMapper; + + @InjectMocks + private AmsBorrowReturnDetailReportServiceImpl service; + + /** 报表只展示已形成借用事实的状态,避免草稿、待确认、驳回单据污染统计口径。 */ + @Test + void selectBorrowReturnDetailReportListShouldAlwaysUseEffectiveStatuses() + { + AmsBorrowReturnDetailReport query = new AmsBorrowReturnDetailReport(); + query.setBorrowUserName("张三"); + when(amsBorrowReturnDetailReportMapper.selectBorrowReturnDetailReportList(query, List.of( + BorrowOrderStatus.BORROWING, BorrowOrderStatus.PENDING_RETURN_CONFIRM, + BorrowOrderStatus.BORROW_RETURNED))).thenReturn(List.of()); + + service.selectBorrowReturnDetailReportList(query); + + ArgumentCaptor reportCaptor = ArgumentCaptor + .forClass(AmsBorrowReturnDetailReport.class); + @SuppressWarnings("unchecked") + ArgumentCaptor> statusCaptor = ArgumentCaptor.forClass(List.class); + verify(amsBorrowReturnDetailReportMapper).selectBorrowReturnDetailReportList(reportCaptor.capture(), + statusCaptor.capture()); + assertEquals("张三", reportCaptor.getValue().getBorrowUserName()); + assertEquals(List.of(BorrowOrderStatus.BORROWING, BorrowOrderStatus.PENDING_RETURN_CONFIRM, + BorrowOrderStatus.BORROW_RETURNED), statusCaptor.getValue()); + } + + @Test + void selectBorrowReturnDetailReportListShouldHandleNullQuery() + { + when(amsBorrowReturnDetailReportMapper.selectBorrowReturnDetailReportList( + any(AmsBorrowReturnDetailReport.class), anyList())).thenReturn(List.of()); + + service.selectBorrowReturnDetailReportList(null); + + verify(amsBorrowReturnDetailReportMapper).selectBorrowReturnDetailReportList( + any(AmsBorrowReturnDetailReport.class), anyList()); + } + + @Test + void selectBorrowReturnDetailReportListShouldReturnMapperRows() + { + AmsBorrowReturnDetailReport row = new AmsBorrowReturnDetailReport(); + row.setBorrowNo("JY202606150001"); + row.setAssetCode("ASSET-001"); + row.setReturnStatus(BorrowOrderStatus.BORROWING); + when(amsBorrowReturnDetailReportMapper.selectBorrowReturnDetailReportList( + any(AmsBorrowReturnDetailReport.class), anyList())).thenReturn(List.of(row)); + + List result = service + .selectBorrowReturnDetailReportList(new AmsBorrowReturnDetailReport()); + + assertEquals(1, result.size()); + assertEquals("JY202606150001", result.get(0).getBorrowNo()); + assertEquals("ASSET-001", result.get(0).getAssetCode()); + assertEquals(BorrowOrderStatus.BORROWING, result.get(0).getReturnStatus()); + } +}