ソースを参照

Merge branch 'hurx'

hurx 2 週間 前
コミット
0b188cf83b
25 ファイル変更2308 行追加0 行削除
  1. 43 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/InvoiceApplicationStatus.java
  2. 123 0
      ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/controller/InvoiceApplicationController.java
  3. 106 0
      ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/controller/ReconApplicationController.java
  4. 165 0
      ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/domain/InvoiceApplication.java
  5. 70 0
      ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/domain/ReconApplication.java
  6. 158 0
      ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/domain/bo/InvoiceApplicationBo.java
  7. 62 0
      ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/domain/bo/ReconApplicationBo.java
  8. 193 0
      ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/domain/vo/InvoiceApplicationVo.java
  9. 77 0
      ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/domain/vo/ReconApplicationVo.java
  10. 15 0
      ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/mapper/InvoiceApplicationMapper.java
  11. 15 0
      ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/mapper/ReconApplicationMapper.java
  12. 78 0
      ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/service/IInvoiceApplicationService.java
  13. 70 0
      ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/service/IReconApplicationService.java
  14. 408 0
      ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/service/impl/InvoiceApplicationServiceImpl.java
  15. 138 0
      ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/service/impl/ReconApplicationServiceImpl.java
  16. 7 0
      ruoyi-modules/ruoyi-bill/src/main/resources/mapper/bill/InvoiceApplicationMapper.xml
  17. 7 0
      ruoyi-modules/ruoyi-bill/src/main/resources/mapper/bill/ReconApplicationMapper.xml
  18. 106 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/ProjectInfoController.java
  19. 77 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/ProjectInfo.java
  20. 69 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/ProjectInfoBo.java
  21. 85 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/ProjectInfoVo.java
  22. 15 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/ProjectInfoMapper.java
  23. 70 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/IProjectInfoService.java
  24. 144 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/ProjectInfoServiceImpl.java
  25. 7 0
      ruoyi-modules/ruoyi-customer/src/main/resources/mapper/customer/ProjectInfoMapper.xml

+ 43 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/InvoiceApplicationStatus.java

@@ -0,0 +1,43 @@
+package org.dromara.common.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum InvoiceApplicationStatus {
+
+    /**
+     * 待审核:用户已提交申请,等待财务审核
+     */
+    PENDING_AUDIT("0", "待审核"),
+
+    /**
+     * 审核通过:财务审核通过,准备开票或正在排期
+     */
+    AUDIT_PASSED("1", "审核通过"),
+
+    /**
+     * 已驳回:财务审核未通过,申请被拒绝
+     */
+    REJECTED("2", "已驳回"),
+
+    /**
+     * 开票中:正在调用税控接口或人工开具发票过程中
+     */
+    INVOICING("3", "开票中"),
+
+    /**
+     * 已完成:发票已成功开具,票号已回填,流程结束
+     */
+    COMPLETED("4", "已完成"),
+
+    /**
+     * 已取消:用户主动撤销申请,或系统超时自动取消
+     */
+    CANCELLED("5", "已取消");
+
+
+    private final String code;
+    private final String info;
+}

+ 123 - 0
ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/controller/InvoiceApplicationController.java

@@ -0,0 +1,123 @@
+package org.dromara.bill.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.bill.domain.vo.InvoiceApplicationVo;
+import org.dromara.bill.domain.bo.InvoiceApplicationBo;
+import org.dromara.bill.service.IInvoiceApplicationService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 开票申请单
+ * 前端访问路由地址为:/bill/invoiceApplication
+ *
+ * @author LionLi
+ * @date 2026-03-26
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/invoiceApplication")
+public class InvoiceApplicationController extends BaseController {
+
+    private final IInvoiceApplicationService invoiceApplicationService;
+
+    /**
+     * 查询开票申请单列表
+     */
+    //@SaCheckPermission("bill:invoiceApplication:list")
+    @GetMapping("/list")
+    public TableDataInfo<InvoiceApplicationVo> list(InvoiceApplicationBo bo, PageQuery pageQuery) {
+        return invoiceApplicationService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出开票申请单列表
+     */
+    //@SaCheckPermission("bill:invoiceApplication:export")
+    @Log(title = "开票申请单", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(InvoiceApplicationBo bo, HttpServletResponse response) {
+        List<InvoiceApplicationVo> list = invoiceApplicationService.queryList(bo);
+        ExcelUtil.exportExcel(list, "开票申请单", InvoiceApplicationVo.class, response);
+    }
+
+    /**
+     * 获取开票申请单详细信息
+     *
+     * @param id 主键
+     */
+    //@SaCheckPermission("bill:invoiceApplication:query")
+    @GetMapping("/{id}")
+    public R<InvoiceApplicationVo> getInfo(@NotNull(message = "主键不能为空")
+                                           @PathVariable("id") Long id) {
+        return R.ok(invoiceApplicationService.queryById(id));
+    }
+
+    /**
+     * 新增开票申请单
+     */
+    //@SaCheckPermission("bill:invoiceApplication:add")
+    @Log(title = "开票申请单", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody InvoiceApplicationBo bo) {
+        Long customerId = LoginHelper.getLoginUser().getCustomerId();
+        bo.setCustomerId(customerId);
+        return toAjax(invoiceApplicationService.insertByBo(bo));
+    }
+
+    /**
+     * 修改开票申请单
+     */
+    //@SaCheckPermission("bill:invoiceApplication:edit")
+    @Log(title = "开票申请单", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody InvoiceApplicationBo bo) {
+        return toAjax(invoiceApplicationService.updateByBo(bo));
+    }
+
+    /**
+     * 删除开票申请单
+     *
+     * @param ids 主键串
+     */
+    //@SaCheckPermission("bill:invoiceApplication:remove")
+    @Log(title = "开票申请单", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable("ids") Long[] ids) {
+        return toAjax(invoiceApplicationService.deleteWithValidByIds(List.of(ids), true));
+    }
+
+    /**
+     * 状态修改
+     */
+    @Log(title = "订单主信息", businessType = BusinessType.UPDATE)
+    @PutMapping("/auditApplication")
+    public R<Void> auditApplication(@RequestBody InvoiceApplicationBo bo) {
+        try {
+            invoiceApplicationService.auditApplication(bo);
+            return R.ok("审核成功");
+        } catch (Exception e) {
+            return R.fail("审核失败: " + e.getMessage());
+        }
+    }
+}

+ 106 - 0
ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/controller/ReconApplicationController.java

@@ -0,0 +1,106 @@
+package org.dromara.bill.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.bill.domain.vo.ReconApplicationVo;
+import org.dromara.bill.domain.bo.ReconApplicationBo;
+import org.dromara.bill.service.IReconApplicationService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 对账申请
+ * 前端访问路由地址为:/bill/reconApplication
+ *
+ * @author LionLi
+ * @date 2026-03-26
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/reconApplication")
+public class ReconApplicationController extends BaseController {
+
+    private final IReconApplicationService reconApplicationService;
+
+    /**
+     * 查询对账申请列表
+     */
+    //@SaCheckPermission("bill:reconApplication:list")
+    @GetMapping("/list")
+    public TableDataInfo<ReconApplicationVo> list(ReconApplicationBo bo, PageQuery pageQuery) {
+        return reconApplicationService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出对账申请列表
+     */
+    //@SaCheckPermission("bill:reconApplication:export")
+    @Log(title = "对账申请", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(ReconApplicationBo bo, HttpServletResponse response) {
+        List<ReconApplicationVo> list = reconApplicationService.queryList(bo);
+        ExcelUtil.exportExcel(list, "对账申请", ReconApplicationVo.class, response);
+    }
+
+    /**
+     * 获取对账申请详细信息
+     *
+     * @param id 主键
+     */
+    //@SaCheckPermission("bill:reconApplication:query")
+    @GetMapping("/{id}")
+    public R<ReconApplicationVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable("id") Long id) {
+        return R.ok(reconApplicationService.queryById(id));
+    }
+
+    /**
+     * 新增对账申请
+     */
+    //@SaCheckPermission("bill:reconApplication:add")
+    @Log(title = "对账申请", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody ReconApplicationBo bo) {
+        return toAjax(reconApplicationService.insertByBo(bo));
+    }
+
+    /**
+     * 修改对账申请
+     */
+    //@SaCheckPermission("bill:reconApplication:edit")
+    @Log(title = "对账申请", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody ReconApplicationBo bo) {
+        return toAjax(reconApplicationService.updateByBo(bo));
+    }
+
+    /**
+     * 删除对账申请
+     *
+     * @param ids 主键串
+     */
+    //@SaCheckPermission("bill:reconApplication:remove")
+    @Log(title = "对账申请", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable("ids") Long[] ids) {
+        return toAjax(reconApplicationService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 165 - 0
ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/domain/InvoiceApplication.java

@@ -0,0 +1,165 @@
+package org.dromara.bill.domain;
+
+import org.dromara.common.tenant.core.TenantEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import java.math.BigDecimal;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+import java.io.Serial;
+
+/**
+ * 开票申请单对象 invoice_application
+ *
+ * @author LionLi
+ * @date 2026-03-26
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("invoice_application")
+public class InvoiceApplication extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 开票申请单号
+     */
+    private String applicationNo;
+
+    /**
+     * 客户ID
+     */
+    private Long customerId;
+
+    /**
+     * 客户名称
+     */
+    private String customerName;
+
+    /**
+     * 发票类型
+     */
+    private String invoiceType;
+
+    /**
+     * 发票抬头
+     */
+    private String invoiceTitle;
+
+    /**
+     * 纳税人识别号/统一社会信用代码
+     */
+    private String taxNumber;
+
+    /**
+     * 注册地址
+     */
+    private String regAddress;
+
+    /**
+     * 注册电话
+     */
+    private String regPhone;
+
+    /**
+     * 开户银行
+     */
+    private String bankName;
+
+    /**
+     * 银行账号
+     */
+    private String bankAccount;
+
+    /**
+     * 接收发票邮箱 (用于电票)
+     */
+    private String receiverEmail;
+
+    /**
+     * 接收短信手机号
+     */
+    private String receiverPhone;
+
+    /**
+     * 申请开票金额 (不含税)
+     */
+    private BigDecimal applyAmount;
+
+    /**
+     * 申请税额
+     */
+    private BigDecimal applyTaxAmount;
+
+    /**
+     * 价税合计总金额
+     */
+    private BigDecimal totalAmount;
+
+    /**
+     * 关联的对账单ID集合
+     */
+    private String statementIds;
+
+    /**
+     * 申请备注/特殊要求
+     */
+    private String remark;
+
+    /**
+     * 申请状态: 0-待审核, 1-审核通过, 2-已驳回, 3-开票中, 4-已完成, 5-已取消
+     */
+    private String status;
+
+    /**
+     * 审核人ID
+     */
+    private Long auditUserId;
+
+    /**
+     * 审核时间
+     */
+    private Date auditTime;
+
+    /**
+     * 审核意见/驳回原因
+     */
+    private String auditRemark;
+
+    /**
+     * 审核通过后生成的正式发票单据ID (关联 statement_invoice.id)
+     */
+    private Long finalInvoiceId;
+
+    /**
+     * 正式发票代码 (开票后回填)
+     */
+    private String invoiceCode;
+
+    /**
+     * 正式发票号码 (开票后回填)
+     */
+    private String invoiceNumber;
+
+    /**
+     * 正式开票日期
+     */
+    private Date invoiceDate;
+
+    /**
+     * 删除标志(0代表存在 2代表删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+
+}

+ 70 - 0
ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/domain/ReconApplication.java

@@ -0,0 +1,70 @@
+package org.dromara.bill.domain;
+
+import org.dromara.common.tenant.core.TenantEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import java.math.BigDecimal;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+import java.io.Serial;
+
+/**
+ * 对账申请对象 recon_application
+ *
+ * @author LionLi
+ * @date 2026-03-26
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("recon_application")
+public class ReconApplication extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 对账单号
+     */
+    private String reconNo;
+
+    /**
+     * 总金额
+     */
+    private BigDecimal totalAmount;
+
+    /**
+     * 申请日期
+     */
+    private Date applyDate;
+
+    /**
+     * 申请对账订单id
+     */
+    private String orderIds;
+
+    /**
+     * 申请状态: 0-未开, 1-已开
+     */
+    private String status;
+
+    /**
+     * 删除标志(0代表存在 2代表删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+
+}

+ 158 - 0
ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/domain/bo/InvoiceApplicationBo.java

@@ -0,0 +1,158 @@
+package org.dromara.bill.domain.bo;
+
+import org.dromara.bill.domain.InvoiceApplication;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+import java.math.BigDecimal;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+/**
+ * 开票申请单业务对象 invoice_application
+ *
+ * @author LionLi
+ * @date 2026-03-26
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = InvoiceApplication.class, reverseConvertGenerate = false)
+public class InvoiceApplicationBo extends BaseEntity {
+
+    /**
+     * 主键ID
+     */
+    private Long id;
+
+    /**
+     * 开票申请单号
+     */
+    private String applicationNo;
+
+    /**
+     * 客户ID
+     */
+    private Long customerId;
+
+    /**
+     * 客户名称
+     */
+    private String customerName;
+
+    /**
+     * 发票类型
+     */
+    //@NotBlank(message = "发票类型不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String invoiceType;
+
+    /**
+     * 发票抬头
+     */
+    private String invoiceTitle;
+
+    /**
+     * 纳税人识别号/统一社会信用代码
+     */
+    private String taxNumber;
+
+    /**
+     * 注册地址
+     */
+    private String regAddress;
+
+    /**
+     * 注册电话
+     */
+    private String regPhone;
+
+    /**
+     * 开户银行
+     */
+    private String bankName;
+
+    /**
+     * 银行账号
+     */
+    private String bankAccount;
+
+    /**
+     * 接收发票邮箱 (用于电票)
+     */
+    private String receiverEmail;
+
+    /**
+     * 接收短信手机号
+     */
+    private String receiverPhone;
+
+    /**
+     * 申请开票金额 (不含税)
+     */
+    private BigDecimal applyAmount;
+
+    /**
+     * 申请税额
+     */
+    private BigDecimal applyTaxAmount;
+
+    /**
+     * 价税合计总金额
+     */
+    private BigDecimal totalAmount;
+
+    /**
+     * 关联的对账单ID集合
+     */
+    private String statementIds;
+
+    /**
+     * 申请备注/特殊要求
+     */
+    private String remark;
+
+    /**
+     * 申请状态: 0-待审核, 1-审核通过, 2-已驳回, 3-开票中, 4-已完成, 5-已取消
+     */
+    private String status;
+
+    /**
+     * 审核人ID
+     */
+    private Long auditUserId;
+
+    /**
+     * 审核时间
+     */
+    private Date auditTime;
+
+    /**
+     * 审核意见/驳回原因
+     */
+    private String auditRemark;
+
+    /**
+     * 审核通过后生成的正式发票单据ID (关联 statement_invoice.id)
+     */
+    private Long finalInvoiceId;
+
+    /**
+     * 正式发票代码 (开票后回填)
+     */
+    private String invoiceCode;
+
+    /**
+     * 正式发票号码 (开票后回填)
+     */
+    private String invoiceNumber;
+
+    /**
+     * 正式开票日期
+     */
+    private Date invoiceDate;
+
+
+}

+ 62 - 0
ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/domain/bo/ReconApplicationBo.java

@@ -0,0 +1,62 @@
+package org.dromara.bill.domain.bo;
+
+import org.dromara.bill.domain.ReconApplication;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+import java.math.BigDecimal;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+/**
+ * 对账申请业务对象 recon_application
+ *
+ * @author LionLi
+ * @date 2026-03-26
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = ReconApplication.class, reverseConvertGenerate = false)
+public class ReconApplicationBo extends BaseEntity {
+
+    /**
+     * 主键
+     */
+    private Long id;
+
+    /**
+     * 对账单号
+     */
+    private String reconNo;
+
+    /**
+     * 总金额
+     */
+    private BigDecimal totalAmount;
+
+    /**
+     * 申请日期
+     */
+    private Date applyDate;
+
+    /**
+     * 申请对账订单id
+     */
+    private String orderIds;
+
+    /**
+     * 申请状态: 0-未开, 1-已开
+     */
+    private String status;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+
+}

+ 193 - 0
ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/domain/vo/InvoiceApplicationVo.java

@@ -0,0 +1,193 @@
+package org.dromara.bill.domain.vo;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import org.dromara.bill.domain.InvoiceApplication;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+
+/**
+ * 开票申请单视图对象 invoice_application
+ *
+ * @author LionLi
+ * @date 2026-03-26
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = InvoiceApplication.class)
+public class InvoiceApplicationVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @ExcelProperty(value = "主键ID")
+    private Long id;
+
+    /**
+     * 开票申请单号
+     */
+    @ExcelProperty(value = "开票申请单号")
+    private String applicationNo;
+
+    /**
+     * 客户ID
+     */
+    @ExcelProperty(value = "客户ID")
+    private Long customerId;
+
+    /**
+     * 客户名称
+     */
+    @ExcelProperty(value = "客户名称")
+    private String customerName;
+
+    /**
+     * 发票类型
+     */
+    @ExcelProperty(value = "发票类型", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(dictType = "invoice_type")
+    private String invoiceType;
+
+    /**
+     * 发票抬头
+     */
+    @ExcelProperty(value = "发票抬头")
+    private String invoiceTitle;
+
+    /**
+     * 纳税人识别号/统一社会信用代码
+     */
+    @ExcelProperty(value = "纳税人识别号/统一社会信用代码")
+    private String taxNumber;
+
+    /**
+     * 注册地址
+     */
+    @ExcelProperty(value = "注册地址")
+    private String regAddress;
+
+    /**
+     * 注册电话
+     */
+    @ExcelProperty(value = "注册电话")
+    private String regPhone;
+
+    /**
+     * 开户银行
+     */
+    @ExcelProperty(value = "开户银行")
+    private String bankName;
+
+    /**
+     * 银行账号
+     */
+    @ExcelProperty(value = "银行账号")
+    private String bankAccount;
+
+    /**
+     * 接收发票邮箱 (用于电票)
+     */
+    @ExcelProperty(value = "接收发票邮箱 (用于电票)")
+    private String receiverEmail;
+
+    /**
+     * 接收短信手机号
+     */
+    @ExcelProperty(value = "接收短信手机号")
+    private String receiverPhone;
+
+    /**
+     * 申请开票金额 (不含税)
+     */
+    @ExcelProperty(value = "申请开票金额 (不含税)")
+    private BigDecimal applyAmount;
+
+    /**
+     * 申请税额
+     */
+    @ExcelProperty(value = "申请税额")
+    private BigDecimal applyTaxAmount;
+
+    /**
+     * 价税合计总金额
+     */
+    @ExcelProperty(value = "价税合计总金额")
+    private BigDecimal totalAmount;
+
+    /**
+     * 关联的对账单ID集合
+     */
+    @ExcelProperty(value = "关联的对账单ID集合")
+    private String statementIds;
+
+    /**
+     * 申请备注/特殊要求
+     */
+    @ExcelProperty(value = "申请备注/特殊要求")
+    private String remark;
+
+    /**
+     * 申请状态: 0-待审核, 1-审核通过, 2-已驳回, 3-开票中, 4-已完成, 5-已取消
+     */
+    @ExcelProperty(value = "申请状态: 0-待审核, 1-审核通过, 2-已驳回, 3-开票中, 4-已完成, 5-已取消", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(dictType = "invoice_application_status")
+    private String status;
+
+    /**
+     * 审核人ID
+     */
+    @ExcelProperty(value = "审核人ID")
+    private Long auditUserId;
+
+    /**
+     * 审核时间
+     */
+    @ExcelProperty(value = "审核时间")
+    private Date auditTime;
+
+    /**
+     * 审核意见/驳回原因
+     */
+    @ExcelProperty(value = "审核意见/驳回原因")
+    private String auditRemark;
+
+    /**
+     * 审核通过后生成的正式发票单据ID (关联 statement_invoice.id)
+     */
+    @ExcelProperty(value = "审核通过后生成的正式发票单据ID (关联 statement_invoice.id)")
+    private Long finalInvoiceId;
+
+    /**
+     * 正式发票代码 (开票后回填)
+     */
+    @ExcelProperty(value = "正式发票代码 (开票后回填)")
+    private String invoiceCode;
+
+    /**
+     * 正式发票号码 (开票后回填)
+     */
+    @ExcelProperty(value = "正式发票号码 (开票后回填)")
+    private String invoiceNumber;
+
+    /**
+     * 正式开票日期
+     */
+    @ExcelProperty(value = "正式开票日期")
+    private Date invoiceDate;
+
+
+}

+ 77 - 0
ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/domain/vo/ReconApplicationVo.java

@@ -0,0 +1,77 @@
+package org.dromara.bill.domain.vo;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import org.dromara.bill.domain.ReconApplication;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+
+/**
+ * 对账申请视图对象 recon_application
+ *
+ * @author LionLi
+ * @date 2026-03-26
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = ReconApplication.class)
+public class ReconApplicationVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    @ExcelProperty(value = "主键")
+    private Long id;
+
+    /**
+     * 对账单号
+     */
+    @ExcelProperty(value = "对账单号")
+    private String reconNo;
+
+    /**
+     * 总金额
+     */
+    @ExcelProperty(value = "总金额")
+    private BigDecimal totalAmount;
+
+    /**
+     * 申请日期
+     */
+    @ExcelProperty(value = "申请日期")
+    private Date applyDate;
+
+    /**
+     * 申请对账订单id
+     */
+    @ExcelProperty(value = "申请对账订单id")
+    private String orderIds;
+
+    /**
+     * 申请状态: 0-未开, 1-已开
+     */
+    @ExcelProperty(value = "申请状态: 0-未开, 1-已开")
+    private String status;
+
+    /**
+     * 备注
+     */
+    @ExcelProperty(value = "备注")
+    private String remark;
+
+
+}

+ 15 - 0
ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/mapper/InvoiceApplicationMapper.java

@@ -0,0 +1,15 @@
+package org.dromara.bill.mapper;
+
+import org.dromara.bill.domain.InvoiceApplication;
+import org.dromara.bill.domain.vo.InvoiceApplicationVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 开票申请单Mapper接口
+ *
+ * @author LionLi
+ * @date 2026-03-26
+ */
+public interface InvoiceApplicationMapper extends BaseMapperPlus<InvoiceApplication, InvoiceApplicationVo> {
+
+}

+ 15 - 0
ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/mapper/ReconApplicationMapper.java

@@ -0,0 +1,15 @@
+package org.dromara.bill.mapper;
+
+import org.dromara.bill.domain.ReconApplication;
+import org.dromara.bill.domain.vo.ReconApplicationVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 对账申请Mapper接口
+ *
+ * @author LionLi
+ * @date 2026-03-26
+ */
+public interface ReconApplicationMapper extends BaseMapperPlus<ReconApplication, ReconApplicationVo> {
+
+}

+ 78 - 0
ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/service/IInvoiceApplicationService.java

@@ -0,0 +1,78 @@
+package org.dromara.bill.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.dromara.bill.domain.InvoiceApplication;
+import org.dromara.bill.domain.vo.InvoiceApplicationVo;
+import org.dromara.bill.domain.bo.InvoiceApplicationBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 开票申请单Service接口
+ *
+ * @author LionLi
+ * @date 2026-03-26
+ */
+public interface IInvoiceApplicationService extends IService<InvoiceApplication> {
+
+    /**
+     * 查询开票申请单
+     *
+     * @param id 主键
+     * @return 开票申请单
+     */
+    InvoiceApplicationVo queryById(Long id);
+
+    /**
+     * 分页查询开票申请单列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 开票申请单分页列表
+     */
+    TableDataInfo<InvoiceApplicationVo> queryPageList(InvoiceApplicationBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询符合条件的开票申请单列表
+     *
+     * @param bo 查询条件
+     * @return 开票申请单列表
+     */
+    List<InvoiceApplicationVo> queryList(InvoiceApplicationBo bo);
+
+    /**
+     * 新增开票申请单
+     *
+     * @param bo 开票申请单
+     * @return 是否新增成功
+     */
+    Boolean insertByBo(InvoiceApplicationBo bo);
+
+    /**
+     * 修改开票申请单
+     *
+     * @param bo 开票申请单
+     * @return 是否修改成功
+     */
+    Boolean updateByBo(InvoiceApplicationBo bo);
+
+    /**
+     * 修改开票申请单状态
+     *
+     * @param bo 开票申请单
+     * @return 是否修改成功
+     */
+    void auditApplication(InvoiceApplicationBo bo);
+
+    /**
+     * 校验并批量删除开票申请单信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+}

+ 70 - 0
ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/service/IReconApplicationService.java

@@ -0,0 +1,70 @@
+package org.dromara.bill.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.dromara.bill.domain.ReconApplication;
+import org.dromara.bill.domain.vo.ReconApplicationVo;
+import org.dromara.bill.domain.bo.ReconApplicationBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 对账申请Service接口
+ *
+ * @author LionLi
+ * @date 2026-03-26
+ */
+public interface IReconApplicationService extends IService<ReconApplication>{
+
+    /**
+     * 查询对账申请
+     *
+     * @param id 主键
+     * @return 对账申请
+     */
+    ReconApplicationVo queryById(Long id);
+
+    /**
+     * 分页查询对账申请列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 对账申请分页列表
+     */
+    TableDataInfo<ReconApplicationVo> queryPageList(ReconApplicationBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询符合条件的对账申请列表
+     *
+     * @param bo 查询条件
+     * @return 对账申请列表
+     */
+    List<ReconApplicationVo> queryList(ReconApplicationBo bo);
+
+    /**
+     * 新增对账申请
+     *
+     * @param bo 对账申请
+     * @return 是否新增成功
+     */
+    Boolean insertByBo(ReconApplicationBo bo);
+
+    /**
+     * 修改对账申请
+     *
+     * @param bo 对账申请
+     * @return 是否修改成功
+     */
+    Boolean updateByBo(ReconApplicationBo bo);
+
+    /**
+     * 校验并批量删除对账申请信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+}

+ 408 - 0
ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/service/impl/InvoiceApplicationServiceImpl.java

@@ -0,0 +1,408 @@
+package org.dromara.bill.service.impl;
+
+import cn.hutool.core.collection.CollectionUtil;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.dromara.bill.domain.*;
+import org.dromara.bill.domain.bo.*;
+import org.dromara.bill.mapper.*;
+import org.dromara.bill.service.IStatementInvoiceService;
+import org.dromara.common.core.enums.InvoiceApplicationStatus;
+import org.dromara.common.core.enums.InvoiceStatus;
+import org.dromara.common.core.enums.LoginType;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.ObjectUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.redis.utils.SequenceUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.system.api.model.LoginUser;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+import org.dromara.bill.domain.vo.InvoiceApplicationVo;
+import org.dromara.bill.service.IInvoiceApplicationService;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 开票申请单Service业务层处理
+ *
+ * @author LionLi
+ * @date 2026-03-26
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class InvoiceApplicationServiceImpl extends ServiceImpl<InvoiceApplicationMapper, InvoiceApplication> implements IInvoiceApplicationService {
+
+    private final InvoiceApplicationMapper baseMapper;
+
+    private final InvoiceInfoMapper invoiceInfoMapper;
+
+    private final StatementInvoiceMapper statementInvoiceMapper;
+
+    private final StatementOrderMapper statementOrderMapper;
+
+    private final StatementDetailMapper statementDetailMapper;
+
+    private final StatementProductMapper statementProductMapper;
+
+    private final IStatementInvoiceService statementInvoiceService;
+
+    private final StatementInvoiceDetailMapper statementInvoiceDetailMapper;
+
+    private final StatementInvoiceProductMapper statementInvoiceProductMapper;
+
+    /**
+     * 查询开票申请单
+     *
+     * @param id 主键
+     * @return 开票申请单
+     */
+    @Override
+    public InvoiceApplicationVo queryById(Long id) {
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 分页查询开票申请单列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 开票申请单分页列表
+     */
+    @Override
+    public TableDataInfo<InvoiceApplicationVo> queryPageList(InvoiceApplicationBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<InvoiceApplication> lqw = buildQueryWrapper(bo);
+        Page<InvoiceApplicationVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询符合条件的开票申请单列表
+     *
+     * @param bo 查询条件
+     * @return 开票申请单列表
+     */
+    @Override
+    public List<InvoiceApplicationVo> queryList(InvoiceApplicationBo bo) {
+        LambdaQueryWrapper<InvoiceApplication> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<InvoiceApplication> buildQueryWrapper(InvoiceApplicationBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<InvoiceApplication> lqw = Wrappers.lambdaQuery();
+        lqw.orderByAsc(InvoiceApplication::getId);
+        lqw.eq(StringUtils.isNotBlank(bo.getApplicationNo()), InvoiceApplication::getApplicationNo, bo.getApplicationNo());
+        lqw.eq(bo.getCustomerId() != null, InvoiceApplication::getCustomerId, bo.getCustomerId());
+        lqw.like(StringUtils.isNotBlank(bo.getCustomerName()), InvoiceApplication::getCustomerName, bo.getCustomerName());
+        lqw.eq(StringUtils.isNotBlank(bo.getInvoiceType()), InvoiceApplication::getInvoiceType, bo.getInvoiceType());
+        lqw.eq(StringUtils.isNotBlank(bo.getInvoiceTitle()), InvoiceApplication::getInvoiceTitle, bo.getInvoiceTitle());
+        lqw.eq(StringUtils.isNotBlank(bo.getTaxNumber()), InvoiceApplication::getTaxNumber, bo.getTaxNumber());
+        lqw.eq(StringUtils.isNotBlank(bo.getRegAddress()), InvoiceApplication::getRegAddress, bo.getRegAddress());
+        lqw.eq(StringUtils.isNotBlank(bo.getRegPhone()), InvoiceApplication::getRegPhone, bo.getRegPhone());
+        lqw.like(StringUtils.isNotBlank(bo.getBankName()), InvoiceApplication::getBankName, bo.getBankName());
+        lqw.eq(StringUtils.isNotBlank(bo.getBankAccount()), InvoiceApplication::getBankAccount, bo.getBankAccount());
+        lqw.eq(StringUtils.isNotBlank(bo.getReceiverEmail()), InvoiceApplication::getReceiverEmail, bo.getReceiverEmail());
+        lqw.eq(StringUtils.isNotBlank(bo.getReceiverPhone()), InvoiceApplication::getReceiverPhone, bo.getReceiverPhone());
+        lqw.eq(bo.getApplyAmount() != null, InvoiceApplication::getApplyAmount, bo.getApplyAmount());
+        lqw.eq(bo.getApplyTaxAmount() != null, InvoiceApplication::getApplyTaxAmount, bo.getApplyTaxAmount());
+        lqw.eq(bo.getTotalAmount() != null, InvoiceApplication::getTotalAmount, bo.getTotalAmount());
+        lqw.eq(StringUtils.isNotBlank(bo.getStatementIds()), InvoiceApplication::getStatementIds, bo.getStatementIds());
+        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), InvoiceApplication::getStatus, bo.getStatus());
+        lqw.eq(bo.getAuditUserId() != null, InvoiceApplication::getAuditUserId, bo.getAuditUserId());
+        lqw.eq(bo.getAuditTime() != null, InvoiceApplication::getAuditTime, bo.getAuditTime());
+        lqw.eq(StringUtils.isNotBlank(bo.getAuditRemark()), InvoiceApplication::getAuditRemark, bo.getAuditRemark());
+        lqw.eq(bo.getFinalInvoiceId() != null, InvoiceApplication::getFinalInvoiceId, bo.getFinalInvoiceId());
+        lqw.eq(StringUtils.isNotBlank(bo.getInvoiceCode()), InvoiceApplication::getInvoiceCode, bo.getInvoiceCode());
+        lqw.eq(StringUtils.isNotBlank(bo.getInvoiceNumber()), InvoiceApplication::getInvoiceNumber, bo.getInvoiceNumber());
+        lqw.eq(bo.getInvoiceDate() != null, InvoiceApplication::getInvoiceDate, bo.getInvoiceDate());
+        lqw.eq(StringUtils.isNotBlank(bo.getPlatformCode()), InvoiceApplication::getPlatformCode, bo.getPlatformCode());
+        return lqw;
+    }
+
+    /**
+     * 新增开票申请单
+     *
+     * @param bo 开票申请单
+     * @return 是否新增成功
+     */
+    @Override
+    public Boolean insertByBo(InvoiceApplicationBo bo) {
+        InvoiceApplication add = MapstructUtils.convert(bo, InvoiceApplication.class);
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    /**
+     * 修改开票申请单
+     *
+     * @param bo 开票申请单
+     * @return 是否修改成功
+     */
+    @Override
+    public Boolean updateByBo(InvoiceApplicationBo bo) {
+        InvoiceApplication update = MapstructUtils.convert(bo, InvoiceApplication.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 修改状态
+     *
+     * @param bo 信息
+     * @return 结果
+     */
+    /**
+     *
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void auditApplication(InvoiceApplicationBo bo) {
+        Long id = bo.getId();
+        // 1. 查询申请单
+        InvoiceApplication application = baseMapper.selectById(id);
+        if (application == null) {
+            throw new RuntimeException("申请单不存在");
+        }
+
+        // 2. 校验状态 (必须是待审核状态才能操作)
+        if (!InvoiceApplicationStatus.PENDING_AUDIT.getCode().equals(application.getStatus())) {
+            throw new RuntimeException("申请单状态异常,当前状态为:" + application.getStatus());
+        }
+
+        // 3. 更新申请单状态
+        InvoiceApplication updateApp = new InvoiceApplication();
+        updateApp.setId(id);
+        updateApp.setStatus(bo.getStatus());
+        updateApp.setAuditUserId(LoginHelper.getLoginUser().getUserId());
+        updateApp.setAuditTime(new Date());
+        updateApp.setAuditRemark(bo.getAuditRemark());
+
+        // 如果被驳回,不需要做其他操作;如果通过,后续可能会调用生成发票方法
+        baseMapper.updateById(updateApp);
+    }
+
+    /**
+     * 根据审核通过的申请单,生成正式发票单据
+     * 逻辑对标 pcInsertByBo,但数据源来自 InvoiceApplication
+     *
+     * @param applicationId 申请单ID
+     * @return 生成的正式发票单ID
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public Long generateInvoiceByApplication(Long applicationId) {
+        // 1. 获取申请单信息
+        InvoiceApplication application = baseMapper.selectById(applicationId);
+        if (application == null) {
+            throw new RuntimeException("申请单不存在");
+        }
+
+        // 2. 校验申请单状态 (必须是审核通过才能生成发票)
+        if (!InvoiceApplicationStatus.AUDIT_PASSED.getCode().equals(application.getStatus())) {
+            throw new RuntimeException("申请单尚未审核通过,无法生成发票");
+        }
+
+        // 3. 解析关联的对账单ID
+        if (StringUtils.isEmpty(application.getStatementIds())) {
+            throw new RuntimeException("申请单未关联对账单");
+        }
+        String[] statementIdArr = application.getStatementIds().split(",");
+        List<Long> statementOrderIds = Arrays.stream(statementIdArr).map(Long::parseLong).collect(Collectors.toList());
+
+        // 4. 查询对账单详情 (用于获取明细和商品)
+        // 注意:这里复用 pcInsertByBo 的逻辑,查对账单是为了拿明细
+        List<StatementOrder> statementOrders = statementOrderMapper.selectBatchIds(statementOrderIds);
+        if (statementOrders.size() != statementOrderIds.size()) {
+            throw new RuntimeException("关联的对账单部分丢失,无法生成发票");
+        }
+
+        // 5. 构建正式发票主单 (StatementInvoice)
+        // 使用申请单中的“快照信息”作为发票抬头,而不是对账单的客户信息
+        StatementInvoice formalInvoice = new StatementInvoice();
+        formalInvoice.setStatementInvoiceNo(SequenceUtils.generateOrderCode("INV")); // 生成正式发票流水号
+        formalInvoice.setCustomerNo(statementOrders.get(0).getCustomerNo()); // 客户编号
+        formalInvoice.setCustomerName(application.getInvoiceTitle()); // 【关键】使用申请时填写的抬头
+        formalInvoice.setInvoiceAmount(application.getTotalAmount()); // 申请金额
+        formalInvoice.setInvoiceStatus(InvoiceStatus.INVOICED.getCode()); // 状态:已开具/待开票
+        formalInvoice.setCreateTime(new Date());
+
+
+        // 保存正式发票主单
+        Integer saved = statementInvoiceMapper.insert(formalInvoice);
+        if (saved <= 0) {
+            throw new RuntimeException("保存正式发票主单失败");
+        }
+        Long formalInvoiceId = formalInvoice.getId();
+
+        // 6. 构建明细和商品列表
+        List<StatementInvoiceDetailBo> detailBoList = new ArrayList<>();
+        List<StatementInvoiceProductBo> productBoList = new ArrayList<>();
+
+        // 6.1 查询所有关联的明细
+        List<StatementDetail> allDetails = statementDetailMapper.selectList(
+            new LambdaQueryWrapper<StatementDetail>()
+                .in(StatementDetail::getStatementOrderId, statementOrderIds)
+        );
+
+        // 6.2 提取 Key 用于查询商品
+        Set<String> orderKeys = allDetails.stream()
+            .map(d -> d.getStatementOrderId() + "_" + d.getOrderId())
+            .collect(Collectors.toSet());
+
+        if (!orderKeys.isEmpty()) {
+            // 查询所有关联的商品
+            List<StatementProduct> allProducts = statementProductMapper.selectList(
+                new LambdaQueryWrapper<StatementProduct>()
+                    .in(StatementProduct::getStatementOrderId, statementOrderIds)
+            );
+
+            // 建立商品映射
+            Map<String, List<StatementProduct>> productMap = allProducts.stream()
+                .collect(Collectors.groupingBy(p -> p.getStatementOrderId() + "_" + p.getOrderId()));
+
+            // 组装 BO 对象
+            for (StatementDetail detail : allDetails) {
+                // --- 组装明细 ---
+                StatementInvoiceDetailBo detailBo = new StatementInvoiceDetailBo();
+                detailBo.setStatementInvoiceId(formalInvoiceId);
+                detailBo.setStatementInvoiceNo(formalInvoice.getStatementInvoiceNo());
+                detailBo.setStatementAmount(detail.getAmount());
+                detailBo.setOrderId(detail.getOrderId());
+                detailBo.setOrderNo(detail.getOrderNo());
+                detailBo.setOrderAmount(detail.getAmount());
+                detailBo.setOrderTime(detail.getOrderTime());
+                detailBo.setSigningDate(detail.getSigningDate());
+                detailBo.setUserId(LoginHelper.getUserId()); // 操作人
+                detailBo.setUserDeptId(LoginHelper.getDeptId());
+                detailBo.setStatementOrderId(detail.getStatementOrderId());
+                detailBo.setStatementOrderNo(detail.getStatementOrderNo());
+                detailBoList.add(detailBo);
+
+                // --- 组装商品 ---
+                String key = detail.getStatementOrderId() + "_" + detail.getOrderId();
+                List<StatementProduct> products = productMap.get(key);
+                if (CollectionUtil.isNotEmpty(products)) {
+                    for (StatementProduct p : products) {
+                        StatementInvoiceProductBo productBo = new StatementInvoiceProductBo();
+                        productBo.setStatementInvoiceId(formalInvoiceId);
+                        productBo.setStatementInvoiceNo(formalInvoice.getStatementInvoiceNo());
+                        productBo.setProductId(p.getProductId());
+                        productBo.setProductNo(p.getProductNo());
+                        productBo.setOrderId(p.getOrderId());
+                        productBo.setOrderNo(p.getOrderNo());
+                        productBo.setItemName(p.getItemName());
+                        productBo.setUnitId(p.getUnitId());
+                        productBo.setUnitName(p.getUnitName());
+                        productBo.setQuantity(p.getQuantity());
+                        productBo.setUnitPrice(p.getUnitPrice());
+                        productBoList.add(productBo);
+                    }
+                }
+            }
+        }
+
+        // 7. 保存子表数据 (复用你提供的公共方法)
+        StatementInvoiceBo saveBo = new StatementInvoiceBo();
+        saveBo.setId(formalInvoiceId);
+        saveBo.setStatementInvoiceNo(formalInvoice.getStatementInvoiceNo());
+        saveBo.setDetailList(detailBoList);
+        saveBo.setProductList(productBoList);
+        saveBo.setInvoiceList(new ArrayList<>()); // 暂不处理发票附件信息
+
+        saveDetailsAndProductsAndInvoiceInfos(saveBo);
+
+        // 8. 【关键】更新申请单状态为“已完成”,并关联正式发票ID
+        InvoiceApplication updateApp = new InvoiceApplication();
+        updateApp.setId(applicationId);
+        updateApp.setStatus(InvoiceApplicationStatus.COMPLETED.getCode());
+        updateApp.setFinalInvoiceId(formalInvoiceId);
+        baseMapper.updateById(updateApp);
+
+        // 9. 【关键】更新对账单的“已开票金额”
+        // 防止同一笔对账单被重复申请开票
+//        BigDecimal amountToInvoice = application.getTotalAmount();
+//       statementOrderMapper.updateInvoiceAmount(statementOrderIds, amountToInvoice);
+
+        return formalInvoiceId;
+    }
+
+    /**
+     * 公共方法:保存明细和商品 发票
+     */
+    private void saveDetailsAndProductsAndInvoiceInfos(StatementInvoiceBo bo) {
+        List<StatementInvoiceDetail> details = new ArrayList<>();
+        List<StatementInvoiceProduct> products = new ArrayList<>();
+        List<InvoiceInfo> invoiceInfoList = new ArrayList<>();
+
+        // 1. 先保存所有明细
+        if (CollectionUtil.isNotEmpty(bo.getDetailList())) {
+            for (StatementInvoiceDetailBo detailBo : bo.getDetailList()) {
+                StatementInvoiceDetail detail = MapstructUtils.convert(detailBo, StatementInvoiceDetail.class);
+                detail.setStatementInvoiceId(bo.getId());
+                detail.setStatementInvoiceNo(bo.getStatementInvoiceNo());
+                details.add(detail);
+            }
+        }
+
+        if (CollectionUtil.isNotEmpty(bo.getProductList())) {
+            for (StatementInvoiceProductBo productBo : bo.getProductList()) {
+                StatementInvoiceProduct product = MapstructUtils.convert(productBo, StatementInvoiceProduct.class);
+                product.setStatementInvoiceId(bo.getId());
+                product.setStatementInvoiceNo(bo.getStatementInvoiceNo());
+                products.add(product);
+            }
+        }
+        if (CollectionUtil.isNotEmpty(bo.getInvoiceList())) {
+            for (InvoiceInfoBo invoiceInfoBo : bo.getInvoiceList()) {
+                InvoiceInfo invoiceInfo = MapstructUtils.convert(invoiceInfoBo, InvoiceInfo.class);
+                invoiceInfo.setStatementInvoiceId(bo.getId());
+                invoiceInfo.setStatementInvoiceNo(bo.getStatementInvoiceNo());
+                invoiceInfoList.add(invoiceInfo);
+            }
+        }
+        if (!invoiceInfoList.isEmpty()) {
+            invoiceInfoMapper.insertBatch(invoiceInfoList);
+        }
+        if (!details.isEmpty()) {
+            statementInvoiceDetailMapper.insertBatch(details);
+        }
+        if (!products.isEmpty()) {
+            statementInvoiceProductMapper.insertBatch(products);
+        }
+    }
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(InvoiceApplication entity) {
+        //TODO 做一些数据校验,如唯一约束
+    }
+
+    /**
+     * 校验并批量删除开票申请单信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if (isValid) {
+            //TODO 做一些业务上的校验,判断是否需要校验
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+}

+ 138 - 0
ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/service/impl/ReconApplicationServiceImpl.java

@@ -0,0 +1,138 @@
+package org.dromara.bill.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.dromara.bill.domain.bo.ReconApplicationBo;
+import org.dromara.bill.domain.vo.ReconApplicationVo;
+import org.dromara.bill.domain.ReconApplication;
+import org.dromara.bill.mapper.ReconApplicationMapper;
+import org.dromara.bill.service.IReconApplicationService;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+
+/**
+ * 对账申请Service业务层处理
+ *
+ * @author LionLi
+ * @date 2026-03-26
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class ReconApplicationServiceImpl  extends ServiceImpl<ReconApplicationMapper, ReconApplication> implements IReconApplicationService {
+
+    private final ReconApplicationMapper baseMapper;
+
+    /**
+     * 查询对账申请
+     *
+     * @param id 主键
+     * @return 对账申请
+     */
+    @Override
+    public ReconApplicationVo queryById(Long id){
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 分页查询对账申请列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 对账申请分页列表
+     */
+    @Override
+    public TableDataInfo<ReconApplicationVo> queryPageList(ReconApplicationBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<ReconApplication> lqw = buildQueryWrapper(bo);
+        Page<ReconApplicationVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询符合条件的对账申请列表
+     *
+     * @param bo 查询条件
+     * @return 对账申请列表
+     */
+    @Override
+    public List<ReconApplicationVo> queryList(ReconApplicationBo bo) {
+        LambdaQueryWrapper<ReconApplication> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<ReconApplication> buildQueryWrapper(ReconApplicationBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<ReconApplication> lqw = Wrappers.lambdaQuery();
+        lqw.orderByAsc(ReconApplication::getId);
+        lqw.eq(StringUtils.isNotBlank(bo.getReconNo()), ReconApplication::getReconNo, bo.getReconNo());
+        lqw.eq(bo.getTotalAmount() != null, ReconApplication::getTotalAmount, bo.getTotalAmount());
+        lqw.eq(bo.getApplyDate() != null, ReconApplication::getApplyDate, bo.getApplyDate());
+        lqw.eq(StringUtils.isNotBlank(bo.getOrderIds()), ReconApplication::getOrderIds, bo.getOrderIds());
+        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), ReconApplication::getStatus, bo.getStatus());
+        lqw.eq(StringUtils.isNotBlank(bo.getPlatformCode()), ReconApplication::getPlatformCode, bo.getPlatformCode());
+        return lqw;
+    }
+
+    /**
+     * 新增对账申请
+     *
+     * @param bo 对账申请
+     * @return 是否新增成功
+     */
+    @Override
+    public Boolean insertByBo(ReconApplicationBo bo) {
+        ReconApplication add = MapstructUtils.convert(bo, ReconApplication.class);
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    /**
+     * 修改对账申请
+     *
+     * @param bo 对账申请
+     * @return 是否修改成功
+     */
+    @Override
+    public Boolean updateByBo(ReconApplicationBo bo) {
+        ReconApplication update = MapstructUtils.convert(bo, ReconApplication.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(ReconApplication entity){
+        //TODO 做一些数据校验,如唯一约束
+    }
+
+    /**
+     * 校验并批量删除对账申请信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if(isValid){
+            //TODO 做一些业务上的校验,判断是否需要校验
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+}

+ 7 - 0
ruoyi-modules/ruoyi-bill/src/main/resources/mapper/bill/InvoiceApplicationMapper.xml

@@ -0,0 +1,7 @@
+<?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="org.dromara.bill.mapper.InvoiceApplicationMapper">
+
+</mapper>

+ 7 - 0
ruoyi-modules/ruoyi-bill/src/main/resources/mapper/bill/ReconApplicationMapper.xml

@@ -0,0 +1,7 @@
+<?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="org.dromara.bill.mapper.ReconApplicationMapper">
+
+</mapper>

+ 106 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/ProjectInfoController.java

@@ -0,0 +1,106 @@
+package org.dromara.customer.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.customer.domain.vo.ProjectInfoVo;
+import org.dromara.customer.domain.bo.ProjectInfoBo;
+import org.dromara.customer.service.IProjectInfoService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 项目信息
+ * 前端访问路由地址为:/customer/projectInfo
+ *
+ * @author LionLi
+ * @date 2026-03-26
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/projectInfo")
+public class ProjectInfoController extends BaseController {
+
+    private final IProjectInfoService projectInfoService;
+
+    /**
+     * 查询项目信息列表
+     */
+    //@SaCheckPermission("customer:projectInfo:list")
+    @GetMapping("/list")
+    public TableDataInfo<ProjectInfoVo> list(ProjectInfoBo bo, PageQuery pageQuery) {
+        return projectInfoService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出项目信息列表
+     */
+    //@SaCheckPermission("customer:projectInfo:export")
+    @Log(title = "项目信息", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(ProjectInfoBo bo, HttpServletResponse response) {
+        List<ProjectInfoVo> list = projectInfoService.queryList(bo);
+        ExcelUtil.exportExcel(list, "项目信息", ProjectInfoVo.class, response);
+    }
+
+    /**
+     * 获取项目信息详细信息
+     *
+     * @param id 主键
+     */
+    //@SaCheckPermission("customer:projectInfo:query")
+    @GetMapping("/{id}")
+    public R<ProjectInfoVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable("id") Long id) {
+        return R.ok(projectInfoService.queryById(id));
+    }
+
+    /**
+     * 新增项目信息
+     */
+    //@SaCheckPermission("customer:projectInfo:add")
+    @Log(title = "项目信息", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody ProjectInfoBo bo) {
+        return toAjax(projectInfoService.insertByBo(bo));
+    }
+
+    /**
+     * 修改项目信息
+     */
+    //@SaCheckPermission("customer:projectInfo:edit")
+    @Log(title = "项目信息", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody ProjectInfoBo bo) {
+        return toAjax(projectInfoService.updateByBo(bo));
+    }
+
+    /**
+     * 删除项目信息
+     *
+     * @param ids 主键串
+     */
+    //@SaCheckPermission("customer:projectInfo:remove")
+    @Log(title = "项目信息", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable("ids") Long[] ids) {
+        return toAjax(projectInfoService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 77 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/ProjectInfo.java

@@ -0,0 +1,77 @@
+package org.dromara.customer.domain;
+
+import org.dromara.common.tenant.core.TenantEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+
+/**
+ * 项目信息对象 project_info
+ *
+ * @author LionLi
+ * @date 2026-03-26
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("project_info")
+public class ProjectInfo extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 项目编号
+     */
+    private String projectNo;
+
+    /**
+     * 项目logo
+     */
+    private String projectLogo;
+
+    /**
+     * 项目名称
+     */
+    private String projectName;
+
+    /**
+     * 项目类型
+     */
+    private String projectType;
+
+    /**
+     * 负责人id
+     */
+    private Long leaderId;
+
+    /**
+     * 负责人
+     */
+    private String leaderName;
+
+    /**
+     * 状态
+     */
+    private String status;
+
+    /**
+     * 删除标志(0代表存在 2代表删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+
+}

+ 69 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/ProjectInfoBo.java

@@ -0,0 +1,69 @@
+package org.dromara.customer.domain.bo;
+
+import org.dromara.customer.domain.ProjectInfo;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+
+/**
+ * 项目信息业务对象 project_info
+ *
+ * @author LionLi
+ * @date 2026-03-26
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = ProjectInfo.class, reverseConvertGenerate = false)
+public class ProjectInfoBo extends BaseEntity {
+
+    /**
+     * 主键ID
+     */
+    private Long id;
+
+    /**
+     * 项目编号
+     */
+    private String projectNo;
+
+    /**
+     * 项目logo
+     */
+    private String projectLogo;
+
+    /**
+     * 项目名称
+     */
+    private String projectName;
+
+    /**
+     * 项目类型
+     */
+    private String projectType;
+
+    /**
+     * 负责人id
+     */
+    private Long leaderId;
+
+    /**
+     * 负责人
+     */
+    private String leaderName;
+
+    /**
+     * 状态
+     */
+    private String status;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+
+}

+ 85 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/ProjectInfoVo.java

@@ -0,0 +1,85 @@
+package org.dromara.customer.domain.vo;
+
+import org.dromara.customer.domain.ProjectInfo;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+/**
+ * 项目信息视图对象 project_info
+ *
+ * @author LionLi
+ * @date 2026-03-26
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = ProjectInfo.class)
+public class ProjectInfoVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @ExcelProperty(value = "主键ID")
+    private Long id;
+
+    /**
+     * 项目编号
+     */
+    @ExcelProperty(value = "项目编号")
+    private String projectNo;
+
+    /**
+     * 项目logo
+     */
+    @ExcelProperty(value = "项目logo")
+    private String projectLogo;
+
+    /**
+     * 项目名称
+     */
+    @ExcelProperty(value = "项目名称")
+    private String projectName;
+
+    /**
+     * 项目类型
+     */
+    @ExcelProperty(value = "项目类型")
+    private String projectType;
+
+    /**
+     * 负责人id
+     */
+    @ExcelProperty(value = "负责人id")
+    private Long leaderId;
+
+    /**
+     * 负责人
+     */
+    @ExcelProperty(value = "负责人")
+    private String leaderName;
+
+    /**
+     * 状态
+     */
+    @ExcelProperty(value = "状态")
+    private String status;
+
+    /**
+     * 备注
+     */
+    @ExcelProperty(value = "备注")
+    private String remark;
+
+    private Date createTime;
+}

+ 15 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/ProjectInfoMapper.java

@@ -0,0 +1,15 @@
+package org.dromara.customer.mapper;
+
+import org.dromara.customer.domain.ProjectInfo;
+import org.dromara.customer.domain.vo.ProjectInfoVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 项目信息Mapper接口
+ *
+ * @author LionLi
+ * @date 2026-03-26
+ */
+public interface ProjectInfoMapper extends BaseMapperPlus<ProjectInfo, ProjectInfoVo> {
+
+}

+ 70 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/IProjectInfoService.java

@@ -0,0 +1,70 @@
+package org.dromara.customer.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.dromara.customer.domain.ProjectInfo;
+import org.dromara.customer.domain.vo.ProjectInfoVo;
+import org.dromara.customer.domain.bo.ProjectInfoBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 项目信息Service接口
+ *
+ * @author LionLi
+ * @date 2026-03-26
+ */
+public interface IProjectInfoService extends IService<ProjectInfo>{
+
+    /**
+     * 查询项目信息
+     *
+     * @param id 主键
+     * @return 项目信息
+     */
+    ProjectInfoVo queryById(Long id);
+
+    /**
+     * 分页查询项目信息列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 项目信息分页列表
+     */
+    TableDataInfo<ProjectInfoVo> queryPageList(ProjectInfoBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询符合条件的项目信息列表
+     *
+     * @param bo 查询条件
+     * @return 项目信息列表
+     */
+    List<ProjectInfoVo> queryList(ProjectInfoBo bo);
+
+    /**
+     * 新增项目信息
+     *
+     * @param bo 项目信息
+     * @return 是否新增成功
+     */
+    Boolean insertByBo(ProjectInfoBo bo);
+
+    /**
+     * 修改项目信息
+     *
+     * @param bo 项目信息
+     * @return 是否修改成功
+     */
+    Boolean updateByBo(ProjectInfoBo bo);
+
+    /**
+     * 校验并批量删除项目信息信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+}

+ 144 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/ProjectInfoServiceImpl.java

@@ -0,0 +1,144 @@
+package org.dromara.customer.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.dromara.customer.domain.bo.ProjectInfoBo;
+import org.dromara.customer.domain.vo.ProjectInfoVo;
+import org.dromara.customer.domain.ProjectInfo;
+import org.dromara.customer.mapper.ProjectInfoMapper;
+import org.dromara.customer.service.IProjectInfoService;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+
+/**
+ * 项目信息Service业务层处理
+ *
+ * @author LionLi
+ * @date 2026-03-26
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class ProjectInfoServiceImpl extends ServiceImpl<ProjectInfoMapper, ProjectInfo> implements IProjectInfoService {
+
+    private final ProjectInfoMapper baseMapper;
+
+    /**
+     * 查询项目信息
+     *
+     * @param id 主键
+     * @return 项目信息
+     */
+    @Override
+    public ProjectInfoVo queryById(Long id) {
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 分页查询项目信息列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 项目信息分页列表
+     */
+    @Override
+    public TableDataInfo<ProjectInfoVo> queryPageList(ProjectInfoBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<ProjectInfo> lqw = buildQueryWrapper(bo);
+        Page<ProjectInfoVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询符合条件的项目信息列表
+     *
+     * @param bo 查询条件
+     * @return 项目信息列表
+     */
+    @Override
+    public List<ProjectInfoVo> queryList(ProjectInfoBo bo) {
+        LambdaQueryWrapper<ProjectInfo> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<ProjectInfo> buildQueryWrapper(ProjectInfoBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<ProjectInfo> lqw = Wrappers.lambdaQuery();
+        lqw.orderByAsc(ProjectInfo::getId);
+        lqw.eq(StringUtils.isNotBlank(bo.getProjectNo()), ProjectInfo::getProjectNo, bo.getProjectNo());
+        lqw.eq(StringUtils.isNotBlank(bo.getProjectLogo()), ProjectInfo::getProjectLogo, bo.getProjectLogo());
+        lqw.like(StringUtils.isNotBlank(bo.getProjectName()), ProjectInfo::getProjectName, bo.getProjectName());
+        lqw.eq(StringUtils.isNotBlank(bo.getProjectType()), ProjectInfo::getProjectType, bo.getProjectType());
+        lqw.eq(bo.getLeaderId() != null, ProjectInfo::getLeaderId, bo.getLeaderId());
+        lqw.like(StringUtils.isNotBlank(bo.getLeaderName()), ProjectInfo::getLeaderName, bo.getLeaderName());
+        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), ProjectInfo::getStatus, bo.getStatus());
+        lqw.eq(StringUtils.isNotBlank(bo.getPlatformCode()), ProjectInfo::getPlatformCode, bo.getPlatformCode());
+        if (params != null) {
+            lqw.between(params.get("beginTime") != null && params.get("endTime") != null,
+                ProjectInfo::getCreateTime, params.get("beginTime"), params.get("endTime"));
+        }
+        return lqw;
+    }
+
+    /**
+     * 新增项目信息
+     *
+     * @param bo 项目信息
+     * @return 是否新增成功
+     */
+    @Override
+    public Boolean insertByBo(ProjectInfoBo bo) {
+        ProjectInfo add = MapstructUtils.convert(bo, ProjectInfo.class);
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    /**
+     * 修改项目信息
+     *
+     * @param bo 项目信息
+     * @return 是否修改成功
+     */
+    @Override
+    public Boolean updateByBo(ProjectInfoBo bo) {
+        ProjectInfo update = MapstructUtils.convert(bo, ProjectInfo.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(ProjectInfo entity) {
+        //TODO 做一些数据校验,如唯一约束
+    }
+
+    /**
+     * 校验并批量删除项目信息信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if (isValid) {
+            //TODO 做一些业务上的校验,判断是否需要校验
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+}

+ 7 - 0
ruoyi-modules/ruoyi-customer/src/main/resources/mapper/customer/ProjectInfoMapper.xml

@@ -0,0 +1,7 @@
+<?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="org.dromara.customer.mapper.ProjectInfoMapper">
+
+</mapper>