package com.doumee.service.business.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.doumee.core.constants.Constants; import com.doumee.core.constants.ResponseStatus; import com.doumee.core.exception.BusinessException; import com.doumee.core.model.LoginUserInfo; import com.doumee.core.model.PageData; import com.doumee.core.model.PageWrap; import com.doumee.biz.system.SystemDictDataBiz; import com.doumee.core.utils.FtpUtil; import com.doumee.core.utils.Utils; import com.doumee.dao.business.InvoiceRecordMapper; import com.doumee.dao.business.OrdersMapper; import com.doumee.dao.business.model.InvoiceRecord; import com.doumee.dao.business.model.Orders; import com.doumee.dao.dto.ApplyInvoiceDTO; import com.doumee.dao.vo.InvoiceRecordSummaryVO; import com.doumee.service.business.InvoiceRecordService; import com.doumee.service.common.EmailService; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils; 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.CollectionUtils; import java.io.File; import java.io.FileOutputStream; import java.util.*; /** * 发票申请记录Service实现 * * @author rk * @date 2026/05/18 */ @Service public class InvoiceRecordServiceImpl implements InvoiceRecordService { @Autowired private InvoiceRecordMapper invoiceRecordMapper; @Autowired private OrdersMapper ordersMapper; @Autowired private EmailService emailService; @Autowired private SystemDictDataBiz systemDictDataBiz; @Override public Integer create(InvoiceRecord invoiceRecord) { LoginUserInfo loginUserInfo = (LoginUserInfo) SecurityUtils.getSubject().getPrincipal(); invoiceRecord.setDeleted(Constants.ZERO); invoiceRecord.setCreateTime(new Date()); invoiceRecord.setCreateUser(loginUserInfo.getId()); invoiceRecord.setUpdateTime(new Date()); invoiceRecord.setUpdateUser(loginUserInfo.getId()); invoiceRecordMapper.insert(invoiceRecord); return invoiceRecord.getId(); } @Override public void deleteById(Integer id) { invoiceRecordMapper.update(new UpdateWrapper().lambda() .set(InvoiceRecord::getDeleted, Constants.ONE) .eq(InvoiceRecord::getId, id)); } @Override public void delete(InvoiceRecord invoiceRecord) { UpdateWrapper deleteWrapper = new UpdateWrapper<>(invoiceRecord); invoiceRecordMapper.delete(deleteWrapper); } @Override public void deleteByIdInBatch(List ids) { if (CollectionUtils.isEmpty(ids)) { return; } for (Integer id : ids) { this.deleteById(id); } } @Override public void updateById(InvoiceRecord invoiceRecord) { LoginUserInfo loginUserInfo = (LoginUserInfo) SecurityUtils.getSubject().getPrincipal(); invoiceRecord.setUpdateTime(new Date()); invoiceRecord.setUpdateUser(loginUserInfo.getId()); invoiceRecordMapper.updateById(invoiceRecord); } @Override public void updateByIdInBatch(List invoiceRecords) { if (CollectionUtils.isEmpty(invoiceRecords)) { return; } for (InvoiceRecord invoiceRecord : invoiceRecords) { this.updateById(invoiceRecord); } } @Override public InvoiceRecord findById(Integer id) { InvoiceRecord invoiceRecord = invoiceRecordMapper.selectById(id); if (Objects.isNull(invoiceRecord)) { throw new BusinessException(ResponseStatus.DATA_EMPTY); } return invoiceRecord; } @Override public InvoiceRecord findOne(InvoiceRecord invoiceRecord) { QueryWrapper wrapper = new QueryWrapper<>(invoiceRecord); return invoiceRecordMapper.selectOne(wrapper); } @Override public List findList(InvoiceRecord invoiceRecord) { QueryWrapper wrapper = new QueryWrapper<>(invoiceRecord); return invoiceRecordMapper.selectList(wrapper); } @Override public PageData findPage(PageWrap pageWrap) { IPage page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity()); QueryWrapper qw = buildAdminQueryWrapper(pageWrap.getModel()); qw.lambda().orderByDesc(InvoiceRecord::getCreateTime); return PageData.from(invoiceRecordMapper.selectPage(page, qw)); } @Override public long count(InvoiceRecord invoiceRecord) { QueryWrapper wrapper = new QueryWrapper<>(invoiceRecord); return invoiceRecordMapper.selectCount(wrapper); } @Override @Transactional(rollbackFor = {Exception.class, BusinessException.class}) public void applyInvoice(ApplyInvoiceDTO dto, Integer memberId) { // 查询订单 Orders order = ordersMapper.selectById(dto.getOrderId()); if (order == null || Constants.equalsInteger(order.getDeleted(), Constants.ONE)) { throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "订单不存在"); } if (!Constants.equalsInteger(order.getMemberId(), memberId)) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "无权操作该订单"); } // 校验发票状态:只有1=可申请、99=开具失败可以申请 if (order.getInvoiceStatus() == null || (!Constants.equalsInteger(order.getInvoiceStatus(), Constants.ONE) && !Constants.equalsInteger(order.getInvoiceStatus(), 99))) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前订单不支持开票申请"); } // 个人/事业单位只能开具电子普通发票 if (Constants.equalsInteger(dto.getOrgType(), Constants.ZERO) && !Constants.equalsInteger(dto.getInvoiceType(), Constants.ZERO)) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "个人/事业单位只能开具电子普通发票"); } // 企业类型:税号必填 if (Constants.equalsInteger(dto.getOrgType(), Constants.ONE) && StringUtils.isBlank(dto.getTaxId())) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "企业类型税号不能为空"); } // 电子专用发票:开户银行、银行账号、企业地址、企业电话必填 if (Constants.equalsInteger(dto.getInvoiceType(), Constants.ONE)) { if (StringUtils.isAnyBlank(dto.getBankName(), dto.getBankAccount(), dto.getCompanyAddr(), dto.getCompanyPhone())) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "电子专用发票需填写开户银行、银行账号、企业地址、企业电话"); } } // 计算开票金额 long payAmount = order.getPayAmount() != null ? order.getPayAmount() : 0L; long refundAmount = order.getRefundAmount() != null ? order.getRefundAmount() : 0L; long invoiceAmount = payAmount - refundAmount; // 创建发票申请记录 InvoiceRecord record = new InvoiceRecord(); record.setOrderId(order.getId()); record.setOrderNo(order.getCode()); record.setMemberId(memberId); record.setOrgType(dto.getOrgType()); record.setInvoiceType(dto.getInvoiceType()); record.setName(dto.getName()); record.setTaxId(dto.getTaxId()); record.setBankName(dto.getBankName()); record.setBankAccount(dto.getBankAccount()); record.setCompanyAddr(dto.getCompanyAddr()); record.setCompanyPhone(dto.getCompanyPhone()); record.setInvoiceAmount(invoiceAmount); record.setStatus(Constants.ZERO); // 0=申请中 record.setDeleted(Constants.ZERO); record.setCreateTime(new Date()); invoiceRecordMapper.insert(record); // 更新订单发票状态为申请中 order.setInvoiceStatus(Constants.TWO); order.setUpdateTime(new Date()); ordersMapper.updateById(order); } @Override public PageData findMemberInvoicePage(PageWrap pageWrap, Integer memberId) { IPage page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity()); QueryWrapper qw = new QueryWrapper<>(); qw.lambda() .eq(InvoiceRecord::getDeleted, Constants.ZERO) .eq(InvoiceRecord::getMemberId, memberId) // .eq(InvoiceRecord::getStatus, Constants.ONE) // 只查已开具成功 .orderByDesc(InvoiceRecord::getCreateTime); return PageData.from(invoiceRecordMapper.selectPage(page, qw)); } @Override public void sendInvoiceEmail(Integer memberId, Integer invoiceRecordId, String email) { InvoiceRecord record = invoiceRecordMapper.selectById(invoiceRecordId); if (record == null || Constants.equalsInteger(record.getDeleted(), Constants.ONE)) { throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "发票记录不存在"); } if (!Constants.equalsInteger(record.getMemberId(), memberId)) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "无权操作该发票记录"); } if (!Constants.equalsInteger(record.getStatus(), Constants.ONE)) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "发票尚未开具成功"); } if (StringUtils.isBlank(record.getFileAddr())) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "发票文件不存在"); } // 拼接文件完整URL String fullUrl = record.getFileAddr(); if (!fullUrl.startsWith("http")) { try { String prefix = systemDictDataBiz.queryByCode(Constants.OSS, Constants.RESOURCE_PATH).getCode(); fullUrl = prefix + fullUrl; } catch (Exception e) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "文件地址配置缺失"); } } // 下载发票文件 byte[] fileBytes; try { fileBytes = new FtpUtil().getOnlineInputsteam(fullUrl); } catch (Exception e) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "发票文件下载失败"); } if (fileBytes == null || fileBytes.length == 0) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "发票文件为空"); } // 写入临时文件 File tempFile = null; try { tempFile = File.createTempFile("invoice_", ".pdf"); try (FileOutputStream fos = new FileOutputStream(tempFile)) { fos.write(fileBytes); } // 构建附件列表 String fileName = "发票_" + record.getOrderNo() + ".pdf"; Map attachment = new HashMap<>(); attachment.put("name", fileName); attachment.put("file", tempFile); boolean success = emailService.sendWithAttachment(email, "您的电子发票", "您好,您的发票详见附件,请查收。", Collections.singletonList(attachment)); if (!success) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "邮件发送失败"); } } catch (BusinessException e) { throw e; } catch (Exception e) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "邮件发送异常"); } finally { if (tempFile != null && tempFile.exists()) { tempFile.delete(); } } } /** * 构建 admin 端发票查询条件(findPage 和 summary 共用) */ private QueryWrapper buildAdminQueryWrapper(InvoiceRecord model) { Utils.MP.blankToNull(model); model.setDeleted(Constants.ZERO); QueryWrapper qw = new QueryWrapper<>(); qw.lambda().eq(InvoiceRecord::getDeleted, Constants.ZERO); if (model.getInvoiceNo() != null && StringUtils.isNotBlank(model.getInvoiceNo())) { qw.lambda().like(InvoiceRecord::getInvoiceNo, model.getInvoiceNo()); } if (model.getInvoiceType() != null) { qw.lambda().eq(InvoiceRecord::getInvoiceType, model.getInvoiceType()); } if (model.getMemberId() != null) { qw.lambda().eq(InvoiceRecord::getMemberId, model.getMemberId()); } if (model.getOrgType() != null) { qw.lambda().eq(InvoiceRecord::getOrgType, model.getOrgType()); } if (model.getName() != null && StringUtils.isNotBlank(model.getName())) { qw.lambda().like(InvoiceRecord::getName, model.getName()); } if (model.getStatus() != null) { qw.lambda().eq(InvoiceRecord::getStatus, model.getStatus()); } if (model.getStartDate() != null) { qw.lambda().ge(InvoiceRecord::getCreateTime, model.getStartDate()); } if (model.getEndDate() != null) { qw.lambda().le(InvoiceRecord::getCreateTime, Utils.Date.getEnd(model.getEndDate())); } return qw; } @Override public InvoiceRecordSummaryVO findPageSummary(PageWrap pageWrap) { QueryWrapper baseQw = buildAdminQueryWrapper(pageWrap.getModel()); // 开票总额:符合条件的所有记录 QueryWrapper totalQw = baseQw.clone(); totalQw.select("IFNULL(SUM(INVOICE_AMOUNT), 0) as invoiceAmount"); Map totalResult = invoiceRecordMapper.selectMaps(totalQw).stream().findFirst().orElse(null); long totalAmount = totalResult != null && totalResult.get("invoiceAmount") != null ? Long.parseLong(totalResult.get("invoiceAmount").toString()) : 0L; // 已开票总额:status=1 QueryWrapper issuedQw = baseQw.clone(); issuedQw.eq("STATUS", Constants.ONE); issuedQw.select("IFNULL(SUM(INVOICE_AMOUNT), 0) as invoiceAmount"); Map issuedResult = invoiceRecordMapper.selectMaps(issuedQw).stream().findFirst().orElse(null); long issuedAmount = issuedResult != null && issuedResult.get("invoiceAmount") != null ? Long.parseLong(issuedResult.get("invoiceAmount").toString()) : 0L; InvoiceRecordSummaryVO vo = new InvoiceRecordSummaryVO(); vo.setTotalAmount(totalAmount); vo.setIssuedAmount(issuedAmount); return vo; } }