Browse Source

feat(pms): 新增备件采购申请功能

- 创建了PartSparePurchaseApplyEntity实体类定义数据库映射
- 实现了PartSparePurchaseApplyService服务层接口和业务逻辑
- 开发了PartSparePurchaseApplyController控制器提供REST API接口
- 配置了MyBatis映射文件实现数据库查询和操作功能
- 添加了查询分页、保存、修改、删除和采购等核心功能
- 实现了采购订单生成和状态管理流程
- 集成了权限控制和数据验证机制
master
qiankanghui 3 weeks ago
parent
commit
cb0a9bad16
  1. 85
      src/main/java/com/xujie/sys/modules/pms/controller/PartSparePurchaseApplyController.java
  2. 27
      src/main/java/com/xujie/sys/modules/pms/data/PartSparePurchaseApplyQueryData.java
  3. 107
      src/main/java/com/xujie/sys/modules/pms/entity/PartSparePurchaseApplyEntity.java
  4. 25
      src/main/java/com/xujie/sys/modules/pms/mapper/PartSparePurchaseApplyMapper.java
  5. 233
      src/main/java/com/xujie/sys/modules/pms/service/Impl/PartSparePurchaseApplyServiceImpl.java
  6. 25
      src/main/java/com/xujie/sys/modules/pms/service/PartSparePurchaseApplyService.java
  7. 66
      src/main/resources/mapper/pms/PartSparePurchaseApplyMapper.xml

85
src/main/java/com/xujie/sys/modules/pms/controller/PartSparePurchaseApplyController.java

@ -0,0 +1,85 @@
package com.xujie.sys.modules.pms.controller;
import com.xujie.sys.common.utils.PageUtils;
import com.xujie.sys.common.utils.R;
import com.xujie.sys.modules.pms.data.PartSparePurchaseApplyQueryData;
import com.xujie.sys.modules.pms.entity.PartSparePurchaseApplyEntity;
import com.xujie.sys.modules.pms.service.PartSparePurchaseApplyService;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@RestController
@RequestMapping("/pms/partspare/purchase/apply")
public class PartSparePurchaseApplyController {
@Autowired
private PartSparePurchaseApplyService partSparePurchaseApplyService;
@PostMapping("/queryPage")
public R queryPage(@RequestBody PartSparePurchaseApplyQueryData data) {
PageUtils page = partSparePurchaseApplyService.queryPage(data);
return R.ok().put("page", page);
}
@PostMapping("/save")
public R save(@RequestBody PartSparePurchaseApplyEntity data) {
partSparePurchaseApplyService.saveApply(data);
return R.ok("保存成功");
}
@PostMapping("/update")
@RequiresPermissions("partSpare:purchase:edit")
public R update(@RequestBody PartSparePurchaseApplyEntity data) {
partSparePurchaseApplyService.updateApply(data);
return R.ok("修改成功");
}
@PostMapping("/delete")
@RequiresPermissions("partSpare:purchase:edit")
public R delete(@RequestBody Object[] ids) {
List<Long> idList = parseIds(ids);
if (idList.isEmpty()) {
return R.error("请先选择需要删除的数据");
}
partSparePurchaseApplyService.deleteApply(idList);
return R.ok("删除成功");
}
@PostMapping("/purchase")
@RequiresPermissions("partSpare:purchase:edit")
public R purchase(@RequestBody Object[] ids) {
List<Long> idList = parseIds(ids);
if (idList.isEmpty()) {
return R.error("请选择需要采购的数据");
}
partSparePurchaseApplyService.purchase(idList);
return R.ok("采购成功");
}
private List<Long> parseIds(Object[] ids) {
List<Long> idList = new ArrayList<>();
if (ids == null) {
return idList;
}
for (Object id : ids) {
if (id == null) {
continue;
}
try {
idList.add(Long.parseLong(String.valueOf(id)));
} catch (Exception e) {
throw new RuntimeException("参数错误,ID格式无效");
}
}
return idList;
}
}

27
src/main/java/com/xujie/sys/modules/pms/data/PartSparePurchaseApplyQueryData.java

@ -0,0 +1,27 @@
package com.xujie.sys.modules.pms.data;
import lombok.Data;
/**
* 备件采购申请(待采购清单)查询条件
*/
@Data
public class PartSparePurchaseApplyQueryData {
private String username;
private String site;
private String buNo;
private String partNo;
private String partDescription;
/** 状态(待采购/已采购) */
private String status;
private Integer page = 1;
private Integer limit = 20;
}

107
src/main/java/com/xujie/sys/modules/pms/entity/PartSparePurchaseApplyEntity.java

@ -0,0 +1,107 @@
package com.xujie.sys.modules.pms.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.util.Date;
/**
* 备件采购申请(待采购清单)实体
*/
@Data
@TableName("part_spare_purchase_apply")
public class PartSparePurchaseApplyEntity {
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
/** 申请单号(后端自动生成) */
@TableField("apply_no")
private String applyNo;
@TableField("site")
private String site;
@TableField("bu_no")
private String buNo;
/** 备品备件编码(保存可空,采购时强制校验) */
@TableField("part_no")
private String partNo;
@TableField("part_description")
private String partDescription;
@TableField("spec")
private String spec;
@TableField("umid")
private String umid;
/** 采购数量 */
@TableField("qty")
private BigDecimal qty;
@TableField("supplier_id")
private String supplierId;
@TableField("supplier_name")
private String supplierName;
/** 物品链接 */
@TableField("item_url")
private String itemUrl;
/** 请购原因 */
@TableField("purchase_reason")
private String purchaseReason;
/** 状态(待采购/已采购) */
@TableField("status")
private String status;
/** 采购后回填的采购订单号 */
@TableField("purchase_order_no")
private String purchaseOrderNo;
/** 申请人 */
@TableField("apply_by")
private String applyBy;
@TableField("apply_time")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date applyTime;
@TableField("created_by")
private String createdBy;
@TableField("created_date")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createdDate;
@TableField("updated_by")
private String updatedBy;
@TableField("updated_date")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updatedDate;
@TableField("is_deleted")
private String isDeleted;
/** BU 描述(仅展示,不落库) */
@TableField(exist = false)
private String buDesc;
}

25
src/main/java/com/xujie/sys/modules/pms/mapper/PartSparePurchaseApplyMapper.java

@ -0,0 +1,25 @@
package com.xujie.sys.modules.pms.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.xujie.sys.modules.pms.data.PartSparePurchaseApplyQueryData;
import com.xujie.sys.modules.pms.entity.PartSparePurchaseApplyEntity;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface PartSparePurchaseApplyMapper extends BaseMapper<PartSparePurchaseApplyEntity> {
IPage<PartSparePurchaseApplyEntity> queryPage(
Page<PartSparePurchaseApplyEntity> page,
@Param("query") PartSparePurchaseApplyQueryData query
);
String getBuDesc(@Param("site") String site, @Param("buNo") String buNo);
/** 采购完成后按主键物理删除中间表记录 */
void deleteApplyByIds(@Param("ids") List<Long> ids);
}

233
src/main/java/com/xujie/sys/modules/pms/service/Impl/PartSparePurchaseApplyServiceImpl.java

@ -0,0 +1,233 @@
package com.xujie.sys.modules.pms.service.Impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xujie.sys.common.utils.PageUtils;
import com.xujie.sys.modules.pms.data.PartSparePurchaseApplyQueryData;
import com.xujie.sys.modules.pms.entity.PartSparePurchaseApplyEntity;
import com.xujie.sys.modules.pms.entity.PurchaseOrder;
import com.xujie.sys.modules.pms.entity.PurchaseOrderHead;
import com.xujie.sys.modules.pms.mapper.PartSparePurchaseApplyMapper;
import com.xujie.sys.modules.pms.mapper.PurchaseOrderHeadMapper;
import com.xujie.sys.modules.pms.mapper.PurchaseOrderMapper;
import com.xujie.sys.modules.pms.service.PartSparePurchaseApplyService;
import com.xujie.sys.modules.sys.entity.SysUserEntity;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;
@Slf4j
@Service
public class PartSparePurchaseApplyServiceImpl
extends ServiceImpl<PartSparePurchaseApplyMapper, PartSparePurchaseApplyEntity>
implements PartSparePurchaseApplyService {
private static final String STATUS_WAIT = "待采购";
@Autowired
private PurchaseOrderMapper purchaseOrderMapper;
@Autowired
private PurchaseOrderHeadMapper purchaseOrderHeadMapper;
@Override
public PageUtils queryPage(PartSparePurchaseApplyQueryData queryData) {
queryData.setUsername(getCurrentUsername());
queryData.setBuNo(normalizeBuNo(queryData.getSite(), queryData.getBuNo()));
long pageNo = queryData.getPage() == null || queryData.getPage() < 1 ? 1 : queryData.getPage();
long pageSize = queryData.getLimit() == null || queryData.getLimit() < 1 ? 20 : queryData.getLimit();
IPage<PartSparePurchaseApplyEntity> page = baseMapper.queryPage(
new Page<>(pageNo, pageSize),
queryData
);
return new PageUtils(page);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void saveApply(PartSparePurchaseApplyEntity data) {
if (!StringUtils.hasText(data.getSite())) {
throw new RuntimeException("请选择BU");
}
if (!StringUtils.hasText(data.getPartDescription())) {
throw new RuntimeException("备品备件名称不能为空");
}
if (data.getQty() == null || data.getQty().compareTo(BigDecimal.ZERO) <= 0) {
throw new RuntimeException("采购数量必须大于0");
}
if (!StringUtils.hasText(data.getPurchaseReason())) {
throw new RuntimeException("请购原因不能为空");
}
data.setBuNo(normalizeBuNo(data.getSite(), data.getBuNo()));
String username = getCurrentUsername();
Date now = new Date();
data.setId(null);
data.setApplyNo(generateApplyNo());
data.setStatus(STATUS_WAIT);
data.setApplyBy(username);
data.setApplyTime(now);
data.setCreatedBy(username);
data.setCreatedDate(now);
data.setUpdatedBy(username);
data.setUpdatedDate(now);
data.setIsDeleted("0");
this.save(data);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateApply(PartSparePurchaseApplyEntity data) {
if (data.getId() == null) {
throw new RuntimeException("参数错误:记录ID不能为空");
}
PartSparePurchaseApplyEntity dbData = getById(data.getId());
if (dbData == null || "1".equals(dbData.getIsDeleted())) {
throw new RuntimeException("记录不存在或已删除,请刷新后重试");
}
if (!StringUtils.hasText(data.getPartDescription())) {
throw new RuntimeException("备品备件名称不能为空");
}
if (data.getQty() == null || data.getQty().compareTo(BigDecimal.ZERO) <= 0) {
throw new RuntimeException("采购数量必须大于0");
}
if (!StringUtils.hasText(data.getPurchaseReason())) {
throw new RuntimeException("请购原因不能为空");
}
dbData.setSite(data.getSite());
dbData.setBuNo(normalizeBuNo(data.getSite(), data.getBuNo()));
dbData.setPartNo(data.getPartNo());
dbData.setPartDescription(data.getPartDescription());
dbData.setSpec(data.getSpec());
dbData.setUmid(data.getUmid());
dbData.setQty(data.getQty());
dbData.setSupplierId(data.getSupplierId());
dbData.setSupplierName(data.getSupplierName());
dbData.setItemUrl(data.getItemUrl());
dbData.setPurchaseReason(data.getPurchaseReason());
dbData.setUpdatedBy(getCurrentUsername());
dbData.setUpdatedDate(new Date());
this.updateById(dbData);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteApply(List<Long> ids) {
if (ids == null || ids.isEmpty()) {
throw new RuntimeException("请先选择需要删除的数据");
}
List<PartSparePurchaseApplyEntity> list = listByIds(ids);
if (list.isEmpty()) {
throw new RuntimeException("记录不存在或已删除,请刷新后重试");
}
String username = getCurrentUsername();
Date now = new Date();
for (PartSparePurchaseApplyEntity apply : list) {
apply.setIsDeleted("1");
apply.setUpdatedBy(username);
apply.setUpdatedDate(now);
updateById(apply);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void purchase(List<Long> ids) {
if (ids == null || ids.isEmpty()) {
throw new RuntimeException("请选择需要采购的数据");
}
List<PartSparePurchaseApplyEntity> list = listByIds(ids);
if (list.size() != ids.size()) {
throw new RuntimeException("部分记录不存在或已删除,请刷新后重试");
}
// 校验:编码非空
for (PartSparePurchaseApplyEntity apply : list) {
if (!StringUtils.hasText(apply.getPartNo())) {
throw new RuntimeException("备品备件编码不能为空,请先补录编码后再采购");
}
}
String username = getCurrentUsername();
Date now = new Date();
// 1、生成采购订单号
String purchaseOrderNo = purchaseOrderMapper.queryPurchaseOrderNo(new PurchaseOrder());
// 2、新增采购订单主表
PartSparePurchaseApplyEntity first = list.get(0);
PurchaseOrderHead head = new PurchaseOrderHead();
head.setIsExport("N");
head.setCreateBy(username);
head.setCreateTime(now);
head.setBuDesc(baseMapper.getBuDesc(first.getSite(), first.getBuNo()));
head.setPurchaseOrderNo(purchaseOrderNo);
head.setStatus("未入库");
purchaseOrderHeadMapper.insertPurchaseOrderHead(head);
Long headId = head.getId();
// 3、逐条写入采购订单明细
long headNum = 1L;
for (PartSparePurchaseApplyEntity apply : list) {
PurchaseOrder order = new PurchaseOrder();
order.setSite(apply.getSite());
order.setBuNo(apply.getBuNo());
order.setPartNo(apply.getPartNo());
order.setQty(apply.getQty());
order.setPurchasereason(apply.getPurchaseReason());
order.setStatus("进行中");
order.setQuantityOfArrival(BigDecimal.ZERO);
order.setCreateBy(username);
order.setCreateTime(now);
order.setPurchaseOrderNo(purchaseOrderNo);
order.setPurchaseOrderHeadId(headId);
order.setHeadNum(headNum++);
purchaseOrderMapper.insert(order);
}
// 4、采购完成后,将数据从中间表物理删除
baseMapper.deleteApplyByIds(ids);
log.info("备件采购申请采购完成,采购订单号:{},已从中间表删除记录数:{}", purchaseOrderNo, ids.size());
}
/**
* 申请单号:A + yyyyMMddHHmmssSSS(毫秒级,保证唯一)
*/
private String generateApplyNo() {
return "A" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"));
}
private String normalizeBuNo(String site, String buNo) {
if (!StringUtils.hasText(buNo)) {
return buNo;
}
String siteText = StringUtils.hasText(site) ? site.trim() : "";
String buText = buNo.trim();
if (!StringUtils.hasText(siteText)) {
return buText;
}
String prefix = siteText + "_";
if (buText.startsWith(prefix)) {
return buText.substring(prefix.length());
}
return buText;
}
private String getCurrentUsername() {
Object principal = SecurityUtils.getSubject().getPrincipal();
if (principal instanceof SysUserEntity) {
return ((SysUserEntity) principal).getUsername();
}
return "system";
}
}

25
src/main/java/com/xujie/sys/modules/pms/service/PartSparePurchaseApplyService.java

@ -0,0 +1,25 @@
package com.xujie.sys.modules.pms.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.xujie.sys.common.utils.PageUtils;
import com.xujie.sys.modules.pms.data.PartSparePurchaseApplyQueryData;
import com.xujie.sys.modules.pms.entity.PartSparePurchaseApplyEntity;
import java.util.List;
public interface PartSparePurchaseApplyService extends IService<PartSparePurchaseApplyEntity> {
PageUtils queryPage(PartSparePurchaseApplyQueryData queryData);
void saveApply(PartSparePurchaseApplyEntity data);
void updateApply(PartSparePurchaseApplyEntity data);
void deleteApply(List<Long> ids);
/**
* 采购:校验选中记录编码非空后,生成采购订单号并落库 purchase_order_head + purchase_order,
* 同时回填中间表状态为"已采购"。
*/
void purchase(List<Long> ids);
}

66
src/main/resources/mapper/pms/PartSparePurchaseApplyMapper.xml

@ -0,0 +1,66 @@
<?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.xujie.sys.modules.pms.mapper.PartSparePurchaseApplyMapper">
<select id="queryPage" resultType="com.xujie.sys.modules.pms.entity.PartSparePurchaseApplyEntity">
select
a.id,
a.apply_no as applyNo,
a.site,
a.bu_no as buNo,
a.part_no as partNo,
a.part_description as partDescription,
a.spec,
a.umid,
a.qty,
a.supplier_id as supplierId,
a.supplier_name as supplierName,
a.item_url as itemUrl,
a.purchase_reason as purchaseReason,
a.status,
a.purchase_order_no as purchaseOrderNo,
a.apply_by as applyBy,
a.apply_time as applyTime,
a.created_by as createdBy,
a.created_date as createdDate,
a.updated_by as updatedBy,
a.updated_date as updatedDate,
a.is_deleted as isDeleted,
dbo.get_bu_desc(a.site, a.bu_no) as buDesc
from part_spare_purchase_apply a
<where>
a.is_deleted = '0'
and a.site in (select site from eam_access_site where username = #{query.username})
and (a.site + '-' + a.bu_no) in (select * from dbo.query_bu(#{query.username}))
<if test="query.site != null and query.site != ''">
and a.site = #{query.site}
</if>
<if test="query.buNo != null and query.buNo != ''">
and a.bu_no = #{query.buNo}
</if>
<if test="query.partNo != null and query.partNo != ''">
and a.part_no like '%' + #{query.partNo} + '%'
</if>
<if test="query.partDescription != null and query.partDescription != ''">
and a.part_description like '%' + #{query.partDescription} + '%'
</if>
<if test="query.status != null and query.status != ''">
and a.status = #{query.status}
</if>
</where>
order by a.apply_time desc, a.id desc
</select>
<select id="getBuDesc" resultType="java.lang.String">
select dbo.get_bu_desc(#{site}, #{buNo})
</select>
<delete id="deleteApplyByIds">
delete from part_spare_purchase_apply
where id in
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
</mapper>
Loading…
Cancel
Save