package com.vincent.rsf.server.manager.service.impl;
|
|
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSONArray;
|
import com.alibaba.fastjson.JSONObject;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.vincent.rsf.framework.exception.CoolException;
|
import com.vincent.rsf.server.manager.entity.OrderPrintTemplate;
|
import com.vincent.rsf.server.manager.mapper.OrderPrintTemplateMapper;
|
import com.vincent.rsf.server.manager.service.OrderPrintTemplateService;
|
import com.vincent.rsf.server.system.entity.User;
|
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import java.util.ArrayList;
|
import java.util.Arrays;
|
import java.util.Collections;
|
import java.util.Date;
|
import java.util.LinkedHashSet;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Objects;
|
import java.util.Set;
|
|
@Service("orderPrintTemplateService")
|
public class OrderPrintTemplateServiceImpl
|
extends ServiceImpl<OrderPrintTemplateMapper, OrderPrintTemplate>
|
implements OrderPrintTemplateService {
|
|
private static final Set<String> SUPPORTED_ELEMENT_TYPES = Collections.unmodifiableSet(
|
new LinkedHashSet<>(Arrays.asList("text", "barcode", "qrcode", "image", "line", "rect", "table"))
|
);
|
|
private static final Set<String> SUPPORTED_TEMPLATE_TYPES = Collections.unmodifiableSet(
|
new LinkedHashSet<>(Arrays.asList("in", "out"))
|
);
|
|
private static final Set<String> SUPPORTED_DOCUMENT_PAPER_SIZES = Collections.unmodifiableSet(
|
new LinkedHashSet<>(Arrays.asList("A4", "A5", "A6", "LETTER"))
|
);
|
|
private static final Set<String> SUPPORTED_DOCUMENT_ORIENTATIONS = Collections.unmodifiableSet(
|
new LinkedHashSet<>(Arrays.asList("portrait", "landscape"))
|
);
|
|
private static final Set<String> SUPPORTED_DOCUMENT_ALIGNMENTS = Collections.unmodifiableSet(
|
new LinkedHashSet<>(Arrays.asList("left", "center", "right"))
|
);
|
|
@Override
|
public List<OrderPrintTemplate> listCurrentTenantTemplates(String type) {
|
String normalizedType = normalizeTemplateType(type);
|
List<OrderPrintTemplate> templates = this.list(new LambdaQueryWrapper<OrderPrintTemplate>()
|
.eq(OrderPrintTemplate::getType, normalizedType)
|
.orderByDesc(OrderPrintTemplate::getIsDefault)
|
.orderByDesc(OrderPrintTemplate::getUpdateTime)
|
.orderByDesc(OrderPrintTemplate::getCreateTime)
|
);
|
return templates == null ? new ArrayList<>() : templates;
|
}
|
|
@Override
|
public OrderPrintTemplate getCurrentTenantTemplate(Long id) {
|
if (id == null) {
|
throw new CoolException("模板ID不能为空");
|
}
|
OrderPrintTemplate template = this.getById(id);
|
if (template == null) {
|
throw new CoolException("模板不存在或已被删除");
|
}
|
normalizeTemplateType(template.getType());
|
return template;
|
}
|
|
@Override
|
public OrderPrintTemplate getCurrentTenantDefaultTemplate(String type) {
|
String normalizedType = normalizeTemplateType(type);
|
OrderPrintTemplate template = this.getOne(new LambdaQueryWrapper<OrderPrintTemplate>()
|
.eq(OrderPrintTemplate::getType, normalizedType)
|
.eq(OrderPrintTemplate::getStatus, 1)
|
.eq(OrderPrintTemplate::getIsDefault, 1)
|
.orderByDesc(OrderPrintTemplate::getUpdateTime)
|
.last("limit 1")
|
);
|
if (template != null) {
|
return template;
|
}
|
return this.getOne(new LambdaQueryWrapper<OrderPrintTemplate>()
|
.eq(OrderPrintTemplate::getType, normalizedType)
|
.eq(OrderPrintTemplate::getStatus, 1)
|
.orderByDesc(OrderPrintTemplate::getUpdateTime)
|
.orderByDesc(OrderPrintTemplate::getCreateTime)
|
.last("limit 1")
|
);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public OrderPrintTemplate saveTemplate(OrderPrintTemplate template) {
|
OrderPrintTemplate normalized = prepareTemplateForSave(template, false);
|
long currentCount = this.count(new LambdaQueryWrapper<OrderPrintTemplate>()
|
.eq(OrderPrintTemplate::getType, normalized.getType())
|
);
|
boolean shouldDefault = Objects.equals(normalized.getIsDefault(), 1) || currentCount == 0;
|
normalized.setIsDefault(shouldDefault ? 1 : 0);
|
if (shouldDefault) {
|
clearCurrentTypeDefaults(normalized.getType());
|
}
|
if (!this.save(normalized)) {
|
throw new CoolException("模板保存失败");
|
}
|
return this.getCurrentTenantTemplate(normalized.getId());
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public OrderPrintTemplate updateTemplate(OrderPrintTemplate template) {
|
if (template == null || template.getId() == null) {
|
throw new CoolException("模板ID不能为空");
|
}
|
OrderPrintTemplate existing = getCurrentTenantTemplate(template.getId());
|
OrderPrintTemplate normalized = prepareTemplateForSave(template, true);
|
normalized.setType(existing.getType());
|
normalized.setTenantId(existing.getTenantId());
|
normalized.setCreateBy(existing.getCreateBy());
|
normalized.setCreateTime(existing.getCreateTime());
|
normalized.setDeleted(existing.getDeleted());
|
boolean shouldDefault = Objects.equals(normalized.getIsDefault(), 1);
|
if (shouldDefault) {
|
clearCurrentTypeDefaults(existing.getType());
|
} else if (Objects.equals(existing.getIsDefault(), 1)) {
|
normalized.setIsDefault(1);
|
}
|
if (!this.updateById(normalized)) {
|
throw new CoolException("模板更新失败");
|
}
|
ensureOneDefaultTemplate(existing.getType());
|
return this.getCurrentTenantTemplate(normalized.getId());
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public boolean removeTemplates(List<Long> ids) {
|
if (ids == null || ids.isEmpty()) {
|
throw new CoolException("请选择要删除的模板");
|
}
|
List<OrderPrintTemplate> templates = this.listByIds(ids);
|
if (templates == null || templates.isEmpty()) {
|
return true;
|
}
|
Set<String> affectedTypes = new LinkedHashSet<>();
|
boolean removedDefault = false;
|
for (OrderPrintTemplate template : templates) {
|
if (template == null) {
|
continue;
|
}
|
affectedTypes.add(normalizeTemplateType(template.getType()));
|
if (Objects.equals(template.getIsDefault(), 1)) {
|
removedDefault = true;
|
}
|
}
|
if (!this.removeByIds(ids)) {
|
throw new CoolException("模板删除失败");
|
}
|
if (removedDefault) {
|
for (String type : affectedTypes) {
|
ensureOneDefaultTemplate(type);
|
}
|
}
|
return true;
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public boolean setDefaultTemplate(Long id) {
|
OrderPrintTemplate template = getCurrentTenantTemplate(id);
|
String type = normalizeTemplateType(template.getType());
|
clearCurrentTypeDefaults(type);
|
boolean updated = this.update(new LambdaUpdateWrapper<OrderPrintTemplate>()
|
.eq(OrderPrintTemplate::getId, template.getId())
|
.set(OrderPrintTemplate::getIsDefault, 1)
|
.set(OrderPrintTemplate::getUpdateBy, resolveCurrentUserId())
|
.set(OrderPrintTemplate::getUpdateTime, new Date())
|
);
|
if (!updated) {
|
throw new CoolException("默认模板设置失败");
|
}
|
return true;
|
}
|
|
private OrderPrintTemplate prepareTemplateForSave(OrderPrintTemplate template, boolean updating) {
|
if (template == null) {
|
throw new CoolException("模板参数不能为空");
|
}
|
Long currentTenantId = resolveCurrentTenantId();
|
Long currentUserId = resolveCurrentUserId();
|
if (currentTenantId == null) {
|
throw new CoolException("当前租户信息缺失");
|
}
|
String type = normalizeTemplateType(template.getType());
|
String name = normalizeText(template.getName());
|
String code = normalizeText(template.getCode());
|
if (name.isEmpty()) {
|
throw new CoolException("模板名称不能为空");
|
}
|
if (code.isEmpty()) {
|
throw new CoolException("模板编码不能为空");
|
}
|
Map<String, Object> canvasJson = template.getCanvasJson();
|
if (canvasJson == null || canvasJson.isEmpty()) {
|
throw new CoolException("模板配置不能为空");
|
}
|
validateCanvasJson(canvasJson);
|
ensureTemplateCodeUnique(type, code, updating ? template.getId() : null);
|
|
Date now = new Date();
|
template.setTenantId(currentTenantId)
|
.setType(type)
|
.setName(name)
|
.setCode(code)
|
.setStatus(template.getStatus() == null ? 1 : template.getStatus())
|
.setIsDefault(Objects.equals(template.getIsDefault(), 1) ? 1 : 0)
|
.setMemo(normalizeText(template.getMemo()))
|
.setUpdateBy(currentUserId)
|
.setUpdateTime(now);
|
if (!updating) {
|
template.setCreateBy(currentUserId);
|
template.setCreateTime(now);
|
}
|
return template;
|
}
|
|
private void ensureTemplateCodeUnique(String type, String code, Long excludeId) {
|
long duplicateCount = this.count(new LambdaQueryWrapper<OrderPrintTemplate>()
|
.eq(OrderPrintTemplate::getType, type)
|
.eq(OrderPrintTemplate::getCode, code)
|
.ne(excludeId != null, OrderPrintTemplate::getId, excludeId)
|
);
|
if (duplicateCount > 0) {
|
throw new CoolException("模板编码已存在,请更换后重试");
|
}
|
}
|
|
private void clearCurrentTypeDefaults(String type) {
|
this.update(new LambdaUpdateWrapper<OrderPrintTemplate>()
|
.eq(OrderPrintTemplate::getType, type)
|
.eq(OrderPrintTemplate::getIsDefault, 1)
|
.set(OrderPrintTemplate::getIsDefault, 0)
|
.set(OrderPrintTemplate::getUpdateBy, resolveCurrentUserId())
|
.set(OrderPrintTemplate::getUpdateTime, new Date())
|
);
|
}
|
|
private void ensureOneDefaultTemplate(String type) {
|
long defaultCount = this.count(new LambdaQueryWrapper<OrderPrintTemplate>()
|
.eq(OrderPrintTemplate::getType, type)
|
.eq(OrderPrintTemplate::getIsDefault, 1)
|
);
|
if (defaultCount > 0) {
|
return;
|
}
|
OrderPrintTemplate newest = this.getOne(new LambdaQueryWrapper<OrderPrintTemplate>()
|
.eq(OrderPrintTemplate::getType, type)
|
.orderByDesc(OrderPrintTemplate::getUpdateTime)
|
.orderByDesc(OrderPrintTemplate::getCreateTime)
|
.last("limit 1")
|
);
|
if (newest == null) {
|
return;
|
}
|
this.update(new LambdaUpdateWrapper<OrderPrintTemplate>()
|
.eq(OrderPrintTemplate::getId, newest.getId())
|
.set(OrderPrintTemplate::getIsDefault, 1)
|
.set(OrderPrintTemplate::getUpdateBy, resolveCurrentUserId())
|
.set(OrderPrintTemplate::getUpdateTime, new Date())
|
);
|
}
|
|
private void validateCanvasJson(Map<String, Object> canvasJson) {
|
JSONObject root = JSONObject.parseObject(JSON.toJSONString(canvasJson));
|
if (root == null) {
|
throw new CoolException("模板配置格式不正确");
|
}
|
if (root.getInteger("version") == null) {
|
throw new CoolException("模板版本不能为空");
|
}
|
if (isDocumentSchema(root)) {
|
validateDocumentSchema(root);
|
return;
|
}
|
validateLegacyCanvasSchema(root);
|
}
|
|
private boolean isDocumentSchema(JSONObject root) {
|
return "document".equals(normalizeText(root.getString("mode")))
|
|| root.containsKey("headerFields")
|
|| root.containsKey("tableColumns")
|
|| root.containsKey("footerFields");
|
}
|
|
private void validateDocumentSchema(JSONObject root) {
|
String title = normalizeText(root.getString("title"));
|
if (title.isEmpty()) {
|
throw new CoolException("单据标题不能为空");
|
}
|
|
JSONObject page = root.getJSONObject("page");
|
if (page == null) {
|
throw new CoolException("纸张配置不能为空");
|
}
|
|
String size = normalizeText(page.getString("size"));
|
if (!SUPPORTED_DOCUMENT_PAPER_SIZES.contains(size)) {
|
throw new CoolException("纸张仅支持 A4、A5、A6 或 LETTER");
|
}
|
|
String orientation = normalizeText(page.getString("orientation"));
|
if (!SUPPORTED_DOCUMENT_ORIENTATIONS.contains(orientation)) {
|
throw new CoolException("纸张方向仅支持 portrait 或 landscape");
|
}
|
|
ensureNumber(page, "marginTop", "上边距");
|
ensureNumber(page, "marginRight", "右边距");
|
ensureNumber(page, "marginBottom", "下边距");
|
ensureNumber(page, "marginLeft", "左边距");
|
|
validateDocumentFields(root.getJSONArray("headerFields"), "页头字段", false);
|
validateDocumentFields(root.getJSONArray("tableColumns"), "明细列", true);
|
validateDocumentFields(root.getJSONArray("footerFields"), "页尾字段", false);
|
|
if (root.getBooleanValue("showBarcode")
|
&& normalizeText(root.getString("barcodeField")).isEmpty()) {
|
throw new CoolException("启用条码时必须设置条码字段");
|
}
|
}
|
|
private void validateDocumentFields(JSONArray fields, String label, boolean tableSection) {
|
if (fields == null) {
|
throw new CoolException(label + "不能为空");
|
}
|
for (int index = 0; index < fields.size(); index++) {
|
JSONObject field = fields.getJSONObject(index);
|
if (field == null) {
|
throw new CoolException(label + "格式不正确");
|
}
|
if (normalizeText(field.getString("key")).isEmpty()) {
|
throw new CoolException(label + "第" + (index + 1) + "项缺少字段标识");
|
}
|
if (normalizeText(field.getString("label")).isEmpty()) {
|
throw new CoolException(label + "第" + (index + 1) + "项缺少字段名称");
|
}
|
if (tableSection) {
|
getPositiveNumber(field, "width", label + "宽度");
|
String align = normalizeText(field.getString("align"));
|
if (!align.isEmpty() && !SUPPORTED_DOCUMENT_ALIGNMENTS.contains(align)) {
|
throw new CoolException(label + "对齐方式仅支持 left、center 或 right");
|
}
|
} else if (field.containsKey("span")) {
|
getPositiveNumber(field, "span", label + "栅格宽度");
|
}
|
}
|
}
|
|
private void validateLegacyCanvasSchema(JSONObject root) {
|
JSONObject canvas = root.getJSONObject("canvas");
|
if (canvas == null) {
|
throw new CoolException("模板画布配置不能为空");
|
}
|
double width = getPositiveNumber(canvas, "width", "画布宽度");
|
double height = getPositiveNumber(canvas, "height", "画布高度");
|
if (width <= 0 || height <= 0) {
|
throw new CoolException("画布尺寸必须大于0");
|
}
|
String unit = normalizeText(canvas.getString("unit"));
|
if (!"mm".equals(unit)) {
|
throw new CoolException("画布单位仅支持 mm");
|
}
|
JSONArray elements = root.getJSONArray("elements");
|
if (elements == null) {
|
throw new CoolException("模板元素不能为空");
|
}
|
for (int index = 0; index < elements.size(); index++) {
|
JSONObject element = elements.getJSONObject(index);
|
if (element == null) {
|
throw new CoolException("模板元素格式不正确");
|
}
|
validateElement(element, index);
|
}
|
}
|
|
private void validateElement(JSONObject element, int index) {
|
String type = normalizeText(element.getString("type"));
|
if (!SUPPORTED_ELEMENT_TYPES.contains(type)) {
|
throw new CoolException("第" + (index + 1) + "个元素类型不支持");
|
}
|
if (normalizeText(element.getString("id")).isEmpty()) {
|
throw new CoolException("第" + (index + 1) + "个元素缺少 ID");
|
}
|
ensureNumber(element, "x", "元素 X 坐标");
|
ensureNumber(element, "y", "元素 Y 坐标");
|
if (!"line".equals(type)) {
|
getPositiveNumber(element, "w", "元素宽度");
|
getPositiveNumber(element, "h", "元素高度");
|
} else {
|
String direction = normalizeText(element.getString("direction"));
|
if (!Arrays.asList("horizontal", "vertical").contains(direction)) {
|
throw new CoolException("线条元素方向仅支持 horizontal 或 vertical");
|
}
|
getPositiveNumber(element, "w", "线条长度");
|
getPositiveNumber(element, "h", "线条粗细");
|
}
|
|
switch (type) {
|
case "text":
|
String contentMode = normalizeText(element.getString("contentMode"));
|
if (!Arrays.asList("static", "template").contains(contentMode)) {
|
throw new CoolException("文本元素内容模式不支持");
|
}
|
if (normalizeText(element.getString("contentTemplate")).isEmpty()) {
|
throw new CoolException("文本元素内容不能为空");
|
}
|
break;
|
case "barcode":
|
if (normalizeText(element.getString("valueTemplate")).isEmpty()) {
|
throw new CoolException("条码元素值模板不能为空");
|
}
|
String symbology = normalizeText(element.getString("symbology"));
|
if (!symbology.isEmpty() && !"CODE128".equals(symbology)) {
|
throw new CoolException("一维码仅支持 CODE128");
|
}
|
break;
|
case "qrcode":
|
if (normalizeText(element.getString("valueTemplate")).isEmpty()) {
|
throw new CoolException("二维码元素值模板不能为空");
|
}
|
break;
|
case "image":
|
if (normalizeText(element.getString("src")).isEmpty()) {
|
throw new CoolException("图片元素地址不能为空");
|
}
|
String objectFit = normalizeText(element.getString("objectFit"));
|
if (!objectFit.isEmpty() && !Arrays.asList("contain", "cover", "fill").contains(objectFit)) {
|
throw new CoolException("图片元素填充方式仅支持 contain、cover 或 fill");
|
}
|
break;
|
case "table":
|
if (element.getJSONArray("columns") == null) {
|
throw new CoolException("表格元素 columns 不能为空");
|
}
|
if (element.getJSONArray("rows") == null) {
|
throw new CoolException("表格元素 rows 不能为空");
|
}
|
if (element.getJSONArray("cells") == null) {
|
throw new CoolException("表格元素 cells 不能为空");
|
}
|
break;
|
default:
|
break;
|
}
|
}
|
|
private void ensureNumber(JSONObject object, String key, String label) {
|
if (object.getBigDecimal(key) == null) {
|
throw new CoolException(label + "不能为空");
|
}
|
}
|
|
private double getPositiveNumber(JSONObject object, String key, String label) {
|
if (object.getBigDecimal(key) == null) {
|
throw new CoolException(label + "不能为空");
|
}
|
double value = object.getBigDecimal(key).doubleValue();
|
if (value <= 0) {
|
throw new CoolException(label + "必须大于0");
|
}
|
return value;
|
}
|
|
private String normalizeTemplateType(String value) {
|
String type = normalizeText(value);
|
if (!SUPPORTED_TEMPLATE_TYPES.contains(type)) {
|
throw new CoolException("模板类型仅支持 in 或 out");
|
}
|
return type;
|
}
|
|
private String normalizeText(String value) {
|
return value == null ? "" : value.trim();
|
}
|
|
private Long resolveCurrentTenantId() {
|
User loginUser = getCurrentUser();
|
return loginUser == null ? null : loginUser.getTenantId();
|
}
|
|
private Long resolveCurrentUserId() {
|
User loginUser = getCurrentUser();
|
return loginUser == null ? null : loginUser.getId();
|
}
|
|
private User getCurrentUser() {
|
try {
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
if (authentication != null && authentication.getPrincipal() instanceof User) {
|
return (User) authentication.getPrincipal();
|
}
|
} catch (Exception ignored) {
|
}
|
return null;
|
}
|
}
|