package jnpf.dmsService; import jnpf.base.ActionResult; import jnpf.base.BillRuleApi; import jnpf.dmsEntity.fenfa.*; import jnpf.dmsMapper.DmsWenjianDayinJishuMapper; import jnpf.exception.DataException; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.*; @Service @RequiredArgsConstructor public class DmsWenjianDayinHaomaService { private final DmsWenjianDayinJishuMapper mapper; private final BillRuleApi billRuleApi; /** 调用方持有任务行锁;新任务创建同事务取号,旧任务仅补齐尚未打印的份号。 */ @Transactional(propagation = Propagation.MANDATORY, rollbackFor = Exception.class) public List ensureTaskNumbers(String tenant, DmsWenjianDayinRenwuEntity task, int consumed) { if (task == null || task.getId() == null || task.getDayinFenshu() == null || task.getDayinFenshu() < 1 || consumed < 0 || consumed > task.getDayinFenshu()) throw new DataException("打印任务份数或已消耗序号无效"); List saved = mapper.selectTaskNumbers(tenant, task.getId()); if (!saved.isEmpty()) { validateSaved(task, consumed, saved); return saved; } int remaining = task.getDayinFenshu() - consumed; if (remaining <= 0) return saved; List allocated = reserve(tenant, task, remaining); for (int i = 0; i < allocated.size(); i++) { DmsWenjianDayinHaoma number = allocated.get(i); number.setId(UUID.randomUUID().toString()); number.setTaskSequence(consumed + i + 1); if (mapper.insertTaskNumber(tenant, task.getId(), number) != 1) throw new DataException("保存任务固定份号失败"); } return allocated; } /** 回传仅验证既有固定号码,绝不在确认阶段补号。 */ public void requireReservedNumbers(String tenant, DmsWenjianDayinRenwuEntity task, int consumed, List frozen) { List saved = mapper.selectTaskNumbers(tenant, task.getId()); validateSaved(task, consumed, saved); List expected = saved.stream() .filter(number -> number.getTaskSequence() > consumed).limit(frozen.size()).toList(); if (!expected.equals(frozen)) throw new DataException("回传号码与任务固定份号不一致"); } private void validateSaved(DmsWenjianDayinRenwuEntity task, int consumed, List saved) { int total = task.getDayinFenshu(); if (saved.isEmpty() || saved.size() > total) throw new DataException("任务固定份号缺失"); int first = total - saved.size() + 1; if (first > consumed + 1) throw new DataException("任务固定份号不完整"); Set unique = new HashSet<>(); for (int i = 0; i < saved.size(); i++) { DmsWenjianDayinHaoma number = saved.get(i); if (number == null || !Objects.equals(number.getTaskSequence(), first + i) || number.getId() == null || number.getCounterId() == null || number.getYear() == null || number.getSequence() == null || number.getSequence() < 1 || number.getSequence() > 9999 || number.getSerialNumber() == null || number.getSerialNumber().isBlank() || !unique.add(number.getSerialNumber()) || !String.format(Locale.ROOT, "%04d%04d", number.getYear(), number.getSequence()).equals(number.getCountSerialNumber())) throw new DataException("任务固定份号不完整或已损坏,不能重新取号"); } } /** 必须与任务创建/首次旧任务补号处于同一事务。已提交的号码永不回收。 */ @Transactional(propagation = Propagation.MANDATORY, rollbackFor = Exception.class) public List reserve(String tenant, DmsWenjianDayinRenwuEntity task, int copies) { if (copies < 1 || copies > 9999 || task.getWenjianBianhao() == null || task.getWenjianBianhao().isBlank() || task.getBanbenhao() == null || task.getBanbenhao().isBlank()) throw new DataException("打印计数组或份数无效"); int year = ZonedDateTime.now(ZoneId.of("Asia/Shanghai")).getYear(); DmsWenjianDayinJishu counter = mapper.reserve(UUID.randomUUID().toString(), tenant, task.getWenjianBianhao(), task.getBanbenhao(), year, copies); if (counter == null) throw new DataException("该文件版本本年度计数流水已超过9999,不能继续取号"); List numbers = new ArrayList<>(); for (int i = counter.getValue() - copies + 1; i <= counter.getValue(); i++) { ActionResult result = billRuleApi.getBillNumber("wenjianDayinLiushuihao"); if (result == null || result.getData() == null || result.getData().isBlank()) throw new DataException("打印唯一流水取号失败"); DmsWenjianDayinHaoma number = new DmsWenjianDayinHaoma(); number.setCounterId(counter.getId()); number.setYear(year); number.setSequence(i); number.setSerialNumber(result.getData()); number.setCountSerialNumber(String.format(Locale.ROOT, "%04d%04d", year, i)); numbers.add(number); } return numbers; } }