|
|
@@ -0,0 +1,163 @@
|
|
|
+package com.ruoyi.system.service.impl;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
+import com.ruoyi.common.exception.ServiceException;
|
|
|
+import com.ruoyi.system.domain.InfoInvoice;
|
|
|
+import com.ruoyi.system.mapper.InfoInvoiceMapper;
|
|
|
+import com.ruoyi.system.service.IInfoInvoiceService;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import java.util.List;
|
|
|
+import java.util.regex.Pattern;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 用户发票抬头 Service 实现(发票信息管理)
|
|
|
+ *
|
|
|
+ * @author foodie
|
|
|
+ * @date 2026-07-24
|
|
|
+ */
|
|
|
+@Service
|
|
|
+public class InfoInvoiceServiceImpl extends ServiceImpl<InfoInvoiceMapper, InfoInvoice> implements IInfoInvoiceService
|
|
|
+{
|
|
|
+ /** 统编:8 位数字 */
|
|
|
+ private static final Pattern UBN_PATTERN = Pattern.compile("^\\d{8}$");
|
|
|
+ /** 自然人凭证:2 大写字母 + 14 数字 */
|
|
|
+ private static final Pattern CARRIER_CDC_PATTERN = Pattern.compile("^[A-Z]{2}\\d{14}$");
|
|
|
+ /** 邮箱格式(宽松校验) */
|
|
|
+ private static final Pattern EMAIL_PATTERN = Pattern.compile("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$");
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<InfoInvoice> listByUserId(Long userId)
|
|
|
+ {
|
|
|
+ return list(new QueryWrapper<InfoInvoice>().eq("user_id", userId).orderByDesc("id"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean saveOrUpdateMine(InfoInvoice invoice, Long userId)
|
|
|
+ {
|
|
|
+ // 强制以当前登录用户为准,前端传的 userId 无效(防越权)
|
|
|
+ invoice.setUserId(userId);
|
|
|
+ validateInvoiceProfile(invoice);
|
|
|
+ // 更新时校验归属:该抬头必须属于当前用户
|
|
|
+ if (invoice.getId() != null)
|
|
|
+ {
|
|
|
+ InfoInvoice exist = getById(invoice.getId());
|
|
|
+ if (exist == null || !userId.equals(exist.getUserId()))
|
|
|
+ {
|
|
|
+ throw new ServiceException("抬头不存在或无权操作");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return saveOrUpdate(invoice);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public InfoInvoice getMine(Long id, Long userId)
|
|
|
+ {
|
|
|
+ InfoInvoice invoice = getById(id);
|
|
|
+ if (invoice == null || !userId.equals(invoice.getUserId()))
|
|
|
+ {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return invoice;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean deleteMine(Long id, Long userId)
|
|
|
+ {
|
|
|
+ InfoInvoice invoice = getById(id);
|
|
|
+ if (invoice == null || !userId.equals(invoice.getUserId()))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return removeById(id);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 保存前中校验(见 contracts/api.md 校验矩阵)。
|
|
|
+ * B2B:公司名 + 统编8位 + 邮箱必填 + 载具空。
|
|
|
+ * B2C:姓名 + 载具必填(0手机条码以/开头、1自然人凭证2字母+14数字、2会员载具须带邮箱)+ 统编空。
|
|
|
+ */
|
|
|
+ private void validateInvoiceProfile(InfoInvoice invoice)
|
|
|
+ {
|
|
|
+ String category = invoice.getCategory();
|
|
|
+ if (!"B2C".equals(category) && !"B2B".equals(category))
|
|
|
+ {
|
|
|
+ throw new ServiceException("发票类型不正确");
|
|
|
+ }
|
|
|
+ if (isBlank(invoice.getTitleName()))
|
|
|
+ {
|
|
|
+ throw new ServiceException("抬头名称不能为空");
|
|
|
+ }
|
|
|
+ if (isBlank(invoice.getBuyerName()))
|
|
|
+ {
|
|
|
+ throw new ServiceException("买方名称不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ if ("B2B".equals(category))
|
|
|
+ {
|
|
|
+ if (invoice.getBuyerUbn() == null || !UBN_PATTERN.matcher(invoice.getBuyerUbn()).matches())
|
|
|
+ {
|
|
|
+ throw new ServiceException("统一编号须为8位数字");
|
|
|
+ }
|
|
|
+ if (isBlank(invoice.getBuyerEmail()) || !EMAIL_PATTERN.matcher(invoice.getBuyerEmail()).matches())
|
|
|
+ {
|
|
|
+ throw new ServiceException("邮箱格式不正确");
|
|
|
+ }
|
|
|
+ if (!isBlank(invoice.getCarrierType()) || !isBlank(invoice.getCarrierNum()))
|
|
|
+ {
|
|
|
+ throw new ServiceException("公司发票无需载具");
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // B2C
|
|
|
+ if (!isBlank(invoice.getBuyerUbn()))
|
|
|
+ {
|
|
|
+ throw new ServiceException("个人发票无需统一编号");
|
|
|
+ }
|
|
|
+ String carrierType = invoice.getCarrierType();
|
|
|
+ if (isBlank(carrierType))
|
|
|
+ {
|
|
|
+ throw new ServiceException("请选择载具类型");
|
|
|
+ }
|
|
|
+ if (isBlank(invoice.getCarrierNum()))
|
|
|
+ {
|
|
|
+ throw new ServiceException("载具号码不能为空");
|
|
|
+ }
|
|
|
+ String carrierNum = invoice.getCarrierNum();
|
|
|
+ switch (carrierType)
|
|
|
+ {
|
|
|
+ case "0": // 手机条码:以 / 开头
|
|
|
+ if (!carrierNum.startsWith("/"))
|
|
|
+ {
|
|
|
+ throw new ServiceException("手机条码须以 / 开头");
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case "1": // 自然人凭证:2 字母 + 14 数字
|
|
|
+ if (!CARRIER_CDC_PATTERN.matcher(carrierNum).matches())
|
|
|
+ {
|
|
|
+ throw new ServiceException("自然人凭证格式不正确");
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case "2": // ezPay 会员载具:须带邮箱
|
|
|
+ if (isBlank(invoice.getBuyerEmail()) || !EMAIL_PATTERN.matcher(invoice.getBuyerEmail()).matches())
|
|
|
+ {
|
|
|
+ throw new ServiceException("会员载具须填写邮箱");
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ throw new ServiceException("载具类型不正确");
|
|
|
+ }
|
|
|
+ // 邮箱选填时也校验格式
|
|
|
+ if (!isBlank(invoice.getBuyerEmail()) && !EMAIL_PATTERN.matcher(invoice.getBuyerEmail()).matches())
|
|
|
+ {
|
|
|
+ throw new ServiceException("邮箱格式不正确");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static boolean isBlank(String s)
|
|
|
+ {
|
|
|
+ return s == null || s.trim().isEmpty();
|
|
|
+ }
|
|
|
+}
|