From cb0a9bad168c5fee3fe87f6873534824e41decb4 Mon Sep 17 00:00:00 2001 From: qiankanghui Date: Tue, 20 Oct 2026 17:12:27 +0800 Subject: [PATCH] =?UTF-8?q?feat(pms):=20=E6=96=B0=E5=A2=9E=E5=A4=87?= =?UTF-8?q?=E4=BB=B6=E9=87=87=E8=B4=AD=E7=94=B3=E8=AF=B7=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 创建了PartSparePurchaseApplyEntity实体类定义数据库映射 - 实现了PartSparePurchaseApplyService服务层接口和业务逻辑 - 开发了PartSparePurchaseApplyController控制器提供REST API接口 - 配置了MyBatis映射文件实现数据库查询和操作功能 - 添加了查询分页、保存、修改、删除和采购等核心功能 - 实现了采购订单生成和状态管理流程 - 集成了权限控制和数据验证机制 --- .../PartSparePurchaseApplyController.java | 85 +++++++ .../data/PartSparePurchaseApplyQueryData.java | 27 ++ .../entity/PartSparePurchaseApplyEntity.java | 107 ++++++++ .../mapper/PartSparePurchaseApplyMapper.java | 25 ++ .../PartSparePurchaseApplyServiceImpl.java | 233 ++++++++++++++++++ .../PartSparePurchaseApplyService.java | 25 ++ .../pms/PartSparePurchaseApplyMapper.xml | 66 +++++ 7 files changed, 568 insertions(+) create mode 100644 src/main/java/com/xujie/sys/modules/pms/controller/PartSparePurchaseApplyController.java create mode 100644 src/main/java/com/xujie/sys/modules/pms/data/PartSparePurchaseApplyQueryData.java create mode 100644 src/main/java/com/xujie/sys/modules/pms/entity/PartSparePurchaseApplyEntity.java create mode 100644 src/main/java/com/xujie/sys/modules/pms/mapper/PartSparePurchaseApplyMapper.java create mode 100644 src/main/java/com/xujie/sys/modules/pms/service/Impl/PartSparePurchaseApplyServiceImpl.java create mode 100644 src/main/java/com/xujie/sys/modules/pms/service/PartSparePurchaseApplyService.java create mode 100644 src/main/resources/mapper/pms/PartSparePurchaseApplyMapper.xml diff --git a/src/main/java/com/xujie/sys/modules/pms/controller/PartSparePurchaseApplyController.java b/src/main/java/com/xujie/sys/modules/pms/controller/PartSparePurchaseApplyController.java new file mode 100644 index 00000000..3b3fe632 --- /dev/null +++ b/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 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 idList = parseIds(ids); + if (idList.isEmpty()) { + return R.error("请选择需要采购的数据"); + } + partSparePurchaseApplyService.purchase(idList); + return R.ok("采购成功"); + } + + private List parseIds(Object[] ids) { + List 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; + } +} diff --git a/src/main/java/com/xujie/sys/modules/pms/data/PartSparePurchaseApplyQueryData.java b/src/main/java/com/xujie/sys/modules/pms/data/PartSparePurchaseApplyQueryData.java new file mode 100644 index 00000000..cacb8f92 --- /dev/null +++ b/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; +} diff --git a/src/main/java/com/xujie/sys/modules/pms/entity/PartSparePurchaseApplyEntity.java b/src/main/java/com/xujie/sys/modules/pms/entity/PartSparePurchaseApplyEntity.java new file mode 100644 index 00000000..9f1430bb --- /dev/null +++ b/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; +} diff --git a/src/main/java/com/xujie/sys/modules/pms/mapper/PartSparePurchaseApplyMapper.java b/src/main/java/com/xujie/sys/modules/pms/mapper/PartSparePurchaseApplyMapper.java new file mode 100644 index 00000000..1b999962 --- /dev/null +++ b/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 { + + IPage queryPage( + Page page, + @Param("query") PartSparePurchaseApplyQueryData query + ); + + String getBuDesc(@Param("site") String site, @Param("buNo") String buNo); + + /** 采购完成后按主键物理删除中间表记录 */ + void deleteApplyByIds(@Param("ids") List ids); +} diff --git a/src/main/java/com/xujie/sys/modules/pms/service/Impl/PartSparePurchaseApplyServiceImpl.java b/src/main/java/com/xujie/sys/modules/pms/service/Impl/PartSparePurchaseApplyServiceImpl.java new file mode 100644 index 00000000..b3437036 --- /dev/null +++ b/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 + 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 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 ids) { + if (ids == null || ids.isEmpty()) { + throw new RuntimeException("请先选择需要删除的数据"); + } + List 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 ids) { + if (ids == null || ids.isEmpty()) { + throw new RuntimeException("请选择需要采购的数据"); + } + List 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"; + } +} diff --git a/src/main/java/com/xujie/sys/modules/pms/service/PartSparePurchaseApplyService.java b/src/main/java/com/xujie/sys/modules/pms/service/PartSparePurchaseApplyService.java new file mode 100644 index 00000000..ce16c49c --- /dev/null +++ b/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 { + + PageUtils queryPage(PartSparePurchaseApplyQueryData queryData); + + void saveApply(PartSparePurchaseApplyEntity data); + + void updateApply(PartSparePurchaseApplyEntity data); + + void deleteApply(List ids); + + /** + * 采购:校验选中记录编码非空后,生成采购订单号并落库 purchase_order_head + purchase_order, + * 同时回填中间表状态为"已采购"。 + */ + void purchase(List ids); +} diff --git a/src/main/resources/mapper/pms/PartSparePurchaseApplyMapper.xml b/src/main/resources/mapper/pms/PartSparePurchaseApplyMapper.xml new file mode 100644 index 00000000..6c71f69d --- /dev/null +++ b/src/main/resources/mapper/pms/PartSparePurchaseApplyMapper.xml @@ -0,0 +1,66 @@ + + + + + + + + + + delete from part_spare_purchase_apply + where id in + + #{id} + + + +