【同步】BOOT 和 CLOUD 的功能

master-jdk17
YunaiV 2026-06-27 08:38:59 -07:00
parent ac1ded1aaf
commit edb292f622
62 changed files with 1400 additions and 1068 deletions

77
.gitignore vendored
View File

@ -1,11 +1,18 @@
######################################################################
# Build Tools
# 查看更多 .gitignore 配置 -> https://help.github.com/articles/ignoring-files/
.gradle
/build/
!gradle/wrapper/gradle-wrapper.jar
target/
!.mvn/wrapper/maven-wrapper.jar
.flattened-pom.xml
######################################################################
# IDE
### STS ###
.apt_generated
.classpath
@ -13,64 +20,36 @@ target/
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
*.class
target/*
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
/build/
nbproject/private/
build/*
nbbuild/
dist/
nbdist/
.nb-gradle/
### admin-web ###
# dependencies
**/node_modules
# roadhog-api-doc ignore
/src/utils/request-temp.js
_roadhog-api-doc
# production
/dist
/.vscode
# misc
.DS_Store
npm-debug.log*
yarn-error.log
/coverage
.idea
yarn.lock
package-lock.json
*bak
.vscode
# visual studio code
.history
######################################################################
# Others
*.log
*.xml.versionsBackup
*.swp
functions/mock
.temp/**
!*/build/*.java
!*/build/*.html
!*/build/*.xml
# umi
.umi
.umi-production
### JRebel ###
rebel.xml
# screenshot
screenshot
.firebase
sessionStore
outputs/
application-my.yaml
/yudao-ui-app/unpackage/
.DS_Store
**/.DS_Store

File diff suppressed because it is too large Load Diff

View File

@ -1371,6 +1371,8 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3034, 1, 'ttt', 'tt', 'iot_ota_task_record_status', 0, 'success', '', NULL, '1', '2025-09-06 00:02:21', '1', '2025-09-06 00:02:31', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3035, 40, '支付宝小程序', '40', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:05:38', '1', '2023-11-04 13:07:16', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3036, 60, 'Admin Uniapp 移动端', '60', 'infra_codegen_front_type', 0, '', '', NULL, '1', '2025-12-16 19:25:51', '1', '2025-12-17 09:46:15', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3037, 42, 'Vben5.0 Antdv Next Schema 模版', '42', 'infra_codegen_front_type', 0, '', '', '', '1', NOW(), '1', NOW(), '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3038, 43, 'Vben5.0 Antdv Next 标准模版', '43', 'infra_codegen_front_type', 0, '', '', '', '1', NOW(), '1', NOW(), '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3040, 1, 'UDP', 'udp', 'iot_protocol_type', 0, '', '', 'UDP 协议', '1', '2026-02-04 00:32:47', '1', '2026-02-04 00:32:47', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3041, 2, 'WebSocket', 'websocket', 'iot_protocol_type', 0, '', '', 'WebSocket 协议', '1', '2026-02-04 00:32:55', '1', '2026-02-04 00:32:55', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3042, 3, 'HTTP', 'http', 'iot_protocol_type', 0, '', '', 'HTTP 协议', '1', '2026-02-04 00:32:55', '1', '2026-02-04 00:32:55', '0');

File diff suppressed because it is too large Load Diff

View File

@ -1371,6 +1371,8 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3034, 1, 'ttt', 'tt', 'iot_ota_task_record_status', 0, 'success', '', NULL, '1', '2025-09-06 00:02:21', '1', '2025-09-06 00:02:31', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3035, 40, '支付宝小程序', '40', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:05:38', '1', '2023-11-04 13:07:16', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3036, 60, 'Admin Uniapp 移动端', '60', 'infra_codegen_front_type', 0, '', '', NULL, '1', '2025-12-16 19:25:51', '1', '2025-12-17 09:46:15', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3037, 42, 'Vben5.0 Antdv Next Schema 模版', '42', 'infra_codegen_front_type', 0, '', '', '', '1', NOW(), '1', NOW(), '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3038, 43, 'Vben5.0 Antdv Next 标准模版', '43', 'infra_codegen_front_type', 0, '', '', '', '1', NOW(), '1', NOW(), '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3040, 1, 'UDP', 'udp', 'iot_protocol_type', 0, '', '', 'UDP 协议', '1', '2026-02-04 00:32:47', '1', '2026-02-04 00:32:47', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3041, 2, 'WebSocket', 'websocket', 'iot_protocol_type', 0, '', '', 'WebSocket 协议', '1', '2026-02-04 00:32:55', '1', '2026-02-04 00:32:55', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3042, 3, 'HTTP', 'http', 'iot_protocol_type', 0, '', '', 'HTTP 协议', '1', '2026-02-04 00:32:55', '1', '2026-02-04 00:32:55', '0');

View File

@ -1323,6 +1323,8 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3034, 1, 'ttt', 'tt', 'iot_ota_task_record_status', 0, 'success', '', NULL, '1', to_date('2025-09-06 00:02:21', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2025-09-06 00:02:31', 'SYYYY-MM-DD HH24:MI:SS'), '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3035, 40, '支付宝小程序', '40', 'system_social_type', 0, '', '', '', '1', to_date('2023-11-04 13:05:38', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2023-11-04 13:07:16', 'SYYYY-MM-DD HH24:MI:SS'), '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3036, 60, 'Admin Uniapp 移动端', '60', 'infra_codegen_front_type', 0, '', '', NULL, '1', to_date('2025-12-16 19:25:51', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2025-12-17 09:46:15', 'SYYYY-MM-DD HH24:MI:SS'), '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3037, 42, 'Vben5.0 Antdv Next Schema 模版', '42', 'infra_codegen_front_type', 0, '', '', '', '1', SYSDATE, '1', SYSDATE, '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3038, 43, 'Vben5.0 Antdv Next 标准模版', '43', 'infra_codegen_front_type', 0, '', '', '', '1', SYSDATE, '1', SYSDATE, '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3040, 1, 'UDP', 'udp', 'iot_protocol_type', 0, '', '', 'UDP 协议', '1', to_date('2026-02-04 00:32:47', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2026-02-04 00:32:47', 'SYYYY-MM-DD HH24:MI:SS'), '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3041, 2, 'WebSocket', 'websocket', 'iot_protocol_type', 0, '', '', 'WebSocket 协议', '1', to_date('2026-02-04 00:32:55', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2026-02-04 00:32:55', 'SYYYY-MM-DD HH24:MI:SS'), '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3042, 3, 'HTTP', 'http', 'iot_protocol_type', 0, '', '', 'HTTP 协议', '1', to_date('2026-02-04 00:32:55', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2026-02-04 00:32:55', 'SYYYY-MM-DD HH24:MI:SS'), '0');

View File

@ -1371,6 +1371,8 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3034, 1, 'ttt', 'tt', 'iot_ota_task_record_status', 0, 'success', '', NULL, '1', '2025-09-06 00:02:21', '1', '2025-09-06 00:02:31', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3035, 40, '支付宝小程序', '40', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:05:38', '1', '2023-11-04 13:07:16', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3036, 60, 'Admin Uniapp 移动端', '60', 'infra_codegen_front_type', 0, '', '', NULL, '1', '2025-12-16 19:25:51', '1', '2025-12-17 09:46:15', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3037, 42, 'Vben5.0 Antdv Next Schema 模版', '42', 'infra_codegen_front_type', 0, '', '', '', '1', NOW(), '1', NOW(), '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3038, 43, 'Vben5.0 Antdv Next 标准模版', '43', 'infra_codegen_front_type', 0, '', '', '', '1', NOW(), '1', NOW(), '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3040, 1, 'UDP', 'udp', 'iot_protocol_type', 0, '', '', 'UDP 协议', '1', '2026-02-04 00:32:47', '1', '2026-02-04 00:32:47', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3041, 2, 'WebSocket', 'websocket', 'iot_protocol_type', 0, '', '', 'WebSocket 协议', '1', '2026-02-04 00:32:55', '1', '2026-02-04 00:32:55', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3042, 3, 'HTTP', 'http', 'iot_protocol_type', 0, '', '', 'HTTP 协议', '1', '2026-02-04 00:32:55', '1', '2026-02-04 00:32:55', '0');

View File

@ -3367,6 +3367,10 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t
GO
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3036, 60, N'Admin Uniapp 移动端', N'60', N'infra_codegen_front_type', 0, N'', N'', NULL, N'1', N'2025-12-16 19:25:51', N'1', N'2025-12-17 09:46:15', N'0')
GO
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3037, 42, N'Vben5.0 Antdv Next Schema 模版', N'42', N'infra_codegen_front_type', 0, N'', N'', N'', N'1', N'2026-05-16 00:00:00', N'1', N'2026-05-16 00:00:00', N'0')
GO
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3038, 43, N'Vben5.0 Antdv Next 标准模版', N'43', N'infra_codegen_front_type', 0, N'', N'', N'', N'1', N'2026-05-16 00:00:00', N'1', N'2026-05-16 00:00:00', N'0')
GO
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3040, 1, N'UDP', N'udp', N'iot_protocol_type', 0, N'', N'', N'UDP 协议', N'1', N'2026-02-04 00:32:47', N'1', N'2026-02-04 00:32:47', N'0')
GO
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3041, 2, N'WebSocket', N'websocket', N'iot_protocol_type', 0, N'', N'', N'WebSocket 协议', N'1', N'2026-02-04 00:32:55', N'1', N'2026-02-04 00:32:55', N'0')

View File

@ -57,10 +57,12 @@ docker load -i dm8_20240715_x86_rh6_rq_single.tar
```Bash
docker compose up -d dm8
# 注意:启动完 dm 后,需要手动再执行如下命令,因为 dm 不支持初始化脚本
docker compose exec dm8 bash -c '/opt/dmdbms/bin/disql SYSDBA/SYSDBA001 \`/tmp/schema.sql'
docker compose exec dm8 bash -c 'printf "SET DEFINE OFF;\n" > /tmp/schema-with-define-off.sql && cat /tmp/schema.sql >> /tmp/schema-with-define-off.sql && /opt/dmdbms/bin/disql SYSDBA/SYSDBA001 \`/tmp/schema-with-define-off.sql'
exit
```
> 注意:项目 DM8 脚本使用 `varchar(n char)` 保持和 MySQL `varchar(n)` 一致的字符长度语义,建议初始化 DM8 时使用 `PAGE_SIZE=16`、`UNICODE_FLAG=1`。`sql/tools/docker-compose.yaml` 已按该配置提供示例。使用 `disql` 导入时需要先执行 `SET DEFINE OFF;`,避免数据里的 `&` 被当作变量替换。
### 1.6 KingbaseES 人大金仓
① 下载人大金仓 Docker 镜像:

View File

@ -871,7 +871,9 @@ class DM8Convertor(Convertor):
type = type.lower()
if type == "varchar":
return f"varchar({size})"
# MySQL varchar(n) is character-oriented. DM8 may treat varchar(n)
# as bytes, so use explicit CHAR semantics for generated scripts.
return f"varchar({size} char)"
if type in ("int", "int unsigned"):
return "int"
if type in ("bigint", "bigint unsigned"):

View File

@ -3,7 +3,6 @@ package cn.iocoder.yudao.framework.common.util.date;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.date.TemporalAccessorUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.DateIntervalEnum;
@ -16,6 +15,7 @@ import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.List;
import java.util.TimeZone;
import static cn.hutool.core.date.DatePattern.*;
@ -33,6 +33,11 @@ public class LocalDateTimeUtils {
public static DateTimeFormatter UTC_MS_WITH_XXX_OFFSET_FORMATTER = createFormatter(UTC_MS_WITH_XXX_OFFSET_PATTERN);
/**
*
*/
private static final ZoneId DEFAULT_ZONE_ID = TimeZone.getTimeZone(DateUtils.TIME_ZONE_DEFAULT).toZoneId();
/**
*
*
@ -65,6 +70,27 @@ public class LocalDateTimeUtils {
return date.isAfter(LocalDateTime.now());
}
/**
* Unix
*
* @param epochSecond Unix
* @return
*/
public static LocalDateTime ofEpochSecond(long epochSecond) {
return ofEpochSecond(epochSecond, DEFAULT_ZONE_ID);
}
/**
* Unix
*
* @param epochSecond Unix
* @param zoneId
* @return
*/
public static LocalDateTime ofEpochSecond(long epochSecond, ZoneId zoneId) {
return LocalDateTime.ofInstant(Instant.ofEpochSecond(epochSecond), zoneId);
}
/**
*
*
@ -393,7 +419,18 @@ public class LocalDateTimeUtils {
* @throws DateTimeException
*/
public static Long toEpochSecond(LocalDateTime sourceDateTime) {
return TemporalAccessorUtil.toInstant(sourceDateTime).getEpochSecond();
return toEpochSecond(sourceDateTime, DEFAULT_ZONE_ID);
}
/**
* {@link LocalDateTime} Unix 1970-01-01T00:00:00Z
*
* @param sourceDateTime
* @param zoneId
* @return 1970-01-01T00:00:00Z epoch second
*/
public static Long toEpochSecond(LocalDateTime sourceDateTime, ZoneId zoneId) {
return sourceDateTime.atZone(zoneId).toEpochSecond();
}
}

View File

@ -196,6 +196,7 @@ public class BpmProcessInstanceController {
@GetMapping("/get-bpmn-model-view")
@Operation(summary = "获取流程实例的 BPMN 模型视图", description = "在【流程详细】界面中,进行调用")
@Parameter(name = "id", description = "流程实例的编号", required = true)
@PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
public CommonResult<BpmProcessInstanceBpmnModelViewRespVO> getProcessInstanceBpmnModelView(
@RequestParam(value = "id") String id) {
return success(processInstanceService.getProcessInstanceBpmnModelView(id));

View File

@ -24,7 +24,7 @@ public enum BpmTaskCandidateStrategyEnum implements ArrayValuable<Integer> {
MULTI_DEPT_LEADER_MULTI(23, "连续多级部门的负责人"),
POST(22, "岗位"),
USER(30, "用户"),
APPROVE_USER_SELECT(34, "审批人自"), // 当前审批人,可在审批时,选择下一个节点的审批人
APPROVE_USER_SELECT(34, "审批人自"), // 当前审批人,可在审批时,选择下一个节点的审批人
START_USER_SELECT(35, "发起人自选"), // 申请人自己,可在提交申请时,选择此节点的审批人
START_USER(36, "发起人自己"), // 申请人自己, 一般紧挨开始节点,常用于发起人信息审核场景
START_USER_DEPT_LEADER(37, "发起人部门负责人"),

View File

@ -958,13 +958,88 @@ public class BpmnModelUtils {
}
/**
*
* , UserTask ,
*
* <ul>
* <li></li>
* <li></li>
* <li></li>
* </ul>
*
* @param currentElement
* @param bpmnModel BPMN
* @param variables
* @return
*/
public static List<UserTask> getNextUserTasks(FlowElement currentElement, BpmnModel bpmnModel,
Map<String, Object> variables) {
return getNextUserTasks(currentElement, bpmnModel, variables, new HashSet<>(), new ArrayList<>());
}
private static List<UserTask> getNextUserTasks(FlowElement currentElement, BpmnModel bpmnModel,
Map<String, Object> variables,
Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
// 1. 根据节点类型决定要遍历的出口连线
// 网关需要根据条件表达式筛选;其它节点直接取所有 outgoing flows
List<SequenceFlow> outgoingFlows;
if (currentElement instanceof Gateway) {
outgoingFlows = getGatewayOutgoingFlows((Gateway) currentElement, variables);
} else {
outgoingFlows = getElementOutgoingFlows(currentElement);
}
if (CollUtil.isEmpty(outgoingFlows)) {
return userTaskList;
}
// 2. 遍历出口连线,递归查找用户任务
for (SequenceFlow outgoingFlow : outgoingFlows) {
// 防止连线成环导致死循环
if (hasSequenceFlow.contains(outgoingFlow.getId())) {
continue;
}
hasSequenceFlow.add(outgoingFlow.getId());
// 获取目标节点
FlowElement targetElement = bpmnModel.getFlowElement(outgoingFlow.getTargetRef());
if (targetElement == null || targetElement instanceof EndEvent) {
continue;
}
if (targetElement instanceof UserTask) {
// 找到用户任务:加入结果
userTaskList.add((UserTask) targetElement);
} else {
// 非用户任务(网关、服务任务、中间事件等):继续递归向下查找
getNextUserTasks(targetElement, bpmnModel, variables, hasSequenceFlow, userTaskList);
}
}
return userTaskList;
}
/**
* 线
*
* @param gateway
* @param variables
* @return 线
*/
private static List<SequenceFlow> getGatewayOutgoingFlows(Gateway gateway, Map<String, Object> variables) {
if (gateway instanceof ExclusiveGateway) {
SequenceFlow matchFlow = findMatchSequenceFlowByExclusiveGateway(gateway, variables);
return matchFlow == null ? Collections.emptyList() : Collections.singletonList(matchFlow);
}
if (gateway instanceof InclusiveGateway) {
return new ArrayList<>(findMatchSequenceFlowsByInclusiveGateway(gateway, variables));
}
// 默认(并行网关等):走所有出口
return gateway.getOutgoingFlows();
}
/**
* ,
*
* @param source
* @return
*/
public static List<UserTask> getNextUserTasks(FlowElement source) {
return getNextUserTasks(source, null, null);
return getNextUserTasks(source, new HashSet<>(), new ArrayList<>());
}
/**

View File

@ -7,7 +7,6 @@ import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
@ -277,11 +276,10 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
processVariables.putAll(reqVO.getProcessVariables());
}
// 3. 获取下一个将要执行的节点集合
// 3.1 获取下一个将要执行的节点集合
FlowElement flowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey());
List<FlowNode> nextFlowNodes = BpmnModelUtils.getNextFlowNodes(flowElement, bpmnModel, processVariables);
// 仅仅获取 UserTask 节点 TODO add from jason如果网关节点和网关节点相连获取下个 UserTask. 貌似有点不准。
List<FlowNode> nextUserTaskList = CollectionUtils.filterList(nextFlowNodes, node -> node instanceof UserTask);
// 3.2 获取 UserTask 节点
List<UserTask> nextUserTaskList = BpmnModelUtils.getNextUserTasks(flowElement, bpmnModel, processVariables);
List<ActivityNode> nextActivityNodes = convertList(nextUserTaskList, node -> new ActivityNode().setId(node.getId())
.setName(node.getName()).setNodeType(BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType())
.setStatus(BpmTaskStatusEnum.RUNNING.getStatus())

View File

@ -669,10 +669,10 @@ public class BpmTaskServiceImpl implements BpmTaskService {
}
// 1. 获取下一个将要执行的节点集合
FlowElement flowElement = bpmnModel.getFlowElement(taskDefinitionKey);
List<FlowNode> nextFlowNodes = getNextFlowNodes(flowElement, bpmnModel, variables);
List<UserTask> nextFlowNodes = getNextUserTasks(flowElement, bpmnModel, variables);
// 2. 校验选择的下一个节点的审批人,是否合法
for (FlowNode nextFlowNode : nextFlowNodes) {
for (UserTask nextFlowNode : nextFlowNodes) {
Integer candidateStrategy = parseCandidateStrategy(nextFlowNode);
// 2.1 情况一:如果节点中的审批人策略为 发起人自选
if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy())) {
@ -698,8 +698,12 @@ public class BpmTaskServiceImpl implements BpmTaskService {
// 2.2 情况二:如果节点中的审批人策略为 审批人,在审批时选择下一个节点的审批人,并且该节点的审批人为空
if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy())) {
// 如果节点存在,但未配置审批人
// 特殊:如果当前节点已经存在审批人,则不允许覆盖。 例如并行节点后,设置的审批人自选节点。 https://t.zsxq.com/daxv1
Map<String, List<Long>> approveUserSelectAssignees = FlowableUtils.getApproveUserSelectAssignees(processInstance.getProcessVariables());
if (approveUserSelectAssignees != null && CollUtil.isNotEmpty(approveUserSelectAssignees.get(nextFlowNode.getId()))) {
continue;
}
// 如果节点存在,但未配置审批人
List<Long> assignees = nextAssignees != null ? nextAssignees.get(nextFlowNode.getId()) : null;
if (CollUtil.isEmpty(assignees)) {
throw exception(PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName());

View File

@ -26,6 +26,7 @@ public interface ErrorCodeConstants {
ErrorCode BUSINESS_DELETE_FAIL_CONTRACT_EXISTS = new ErrorCode(1_020_002_001, "商机已关联合同,不能删除");
ErrorCode BUSINESS_UPDATE_STATUS_FAIL_END_STATUS = new ErrorCode(1_020_002_002, "更新商机状态失败,原因:已经是结束状态");
ErrorCode BUSINESS_UPDATE_STATUS_FAIL_STATUS_EQUALS = new ErrorCode(1_020_002_003, "更新商机状态失败,原因:已经是该状态");
ErrorCode BUSINESS_CONTACT_CUSTOMER_NOT_MATCH = new ErrorCode(1_020_002_004, "商机关联的联系人不属于该客户");
// ========== 联系人管理 1-020-003-000 ==========
ErrorCode CONTACT_NOT_EXISTS = new ErrorCode(1_020_003_000, "联系人不存在");

View File

@ -138,6 +138,26 @@ public class CrmBusinessController {
.setCustomerId(business.getCustomerId())));
}
@GetMapping("/list-by-customer")
@Operation(summary = "获得商机列表,基于指定客户")
@Parameter(name = "customerId", description = "客户编号", required = true)
@PreAuthorize("@ss.hasPermission('crm:business:query')")
public CommonResult<List<CrmBusinessRespVO>> getBusinessListByCustomer(@RequestParam("customerId") Long customerId) {
List<CrmBusinessDO> list = businessService.getBusinessListByCustomerId(customerId);
return success(convertList(list, business -> // 只返回 id、name 字段
new CrmBusinessRespVO().setId(business.getId()).setName(business.getName())
.setCustomerId(business.getCustomerId())));
}
@GetMapping("/list-by-contact")
@Operation(summary = "获得商机列表,基于指定联系人")
@Parameter(name = "contactId", description = "联系人编号", required = true)
@PreAuthorize("@ss.hasPermission('crm:business:query')")
public CommonResult<List<CrmBusinessRespVO>> getBusinessListByContact(@RequestParam("contactId") Long contactId) {
List<CrmBusinessDO> list = businessService.getBusinessListByContact(contactId);
return success(buildBusinessDetailList(list));
}
@GetMapping("/page")
@Operation(summary = "获得商机分页")
@PreAuthorize("@ss.hasPermission('crm:business:query')")

View File

@ -114,6 +114,17 @@ public class CrmContactController {
.setCustomerId(contact.getCustomerId())));
}
@GetMapping("/list-by-customer")
@Operation(summary = "获得联系人列表,基于指定客户")
@Parameter(name = "customerId", description = "客户编号", required = true)
@PreAuthorize("@ss.hasPermission('crm:contact:query')")
public CommonResult<List<CrmContactRespVO>> getContactListByCustomer(@RequestParam("customerId") Long customerId) {
List<CrmContactDO> list = contactService.getContactListByCustomerId(customerId);
return success(convertList(list, contact -> // 只返回 id、name 字段
new CrmContactRespVO().setId(contact.getId()).setName(contact.getName())
.setCustomerId(contact.getCustomerId())));
}
@GetMapping("/page")
@Operation(summary = "获得联系人分页")
@PreAuthorize("@ss.hasPermission('crm:contact:query')")

View File

@ -11,9 +11,12 @@ import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecor
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
import cn.iocoder.yudao.module.crm.service.followup.CrmFollowUpRecordService;
import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
@ -27,10 +30,13 @@ import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CRM_PERMISSION_DENIED;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.FOLLOW_UP_RECORD_NOT_EXISTS;
@Tag(name = "管理后台 - 跟进记录")
@ -45,6 +51,8 @@ public class CrmFollowUpRecordController {
private CrmContactService contactService;
@Resource
private CrmBusinessService businessService;
@Resource
private CrmPermissionService permissionService;
@Resource
private AdminUserApi adminUserApi;
@ -68,6 +76,13 @@ public class CrmFollowUpRecordController {
@Parameter(name = "id", description = "编号", required = true, example = "1024")
public CommonResult<CrmFollowUpRecordRespVO> getFollowUpRecord(@RequestParam("id") Long id) {
CrmFollowUpRecordDO followUpRecord = followUpRecordService.getFollowUpRecord(id);
if (followUpRecord == null) {
throw exception(FOLLOW_UP_RECORD_NOT_EXISTS);
}
if (!permissionService.hasPermission(followUpRecord.getBizType(), followUpRecord.getBizId(),
getLoginUserId(), CrmPermissionLevelEnum.READ)) {
throw exception(CRM_PERMISSION_DENIED, CrmBizTypeEnum.getNameByType(followUpRecord.getBizType()));
}
return success(BeanUtils.toBean(followUpRecord, CrmFollowUpRecordRespVO.class));
}

View File

@ -65,6 +65,10 @@ public interface CrmBusinessMapper extends BaseMapperX<CrmBusinessDO> {
.eq(CrmBusinessDO::getOwnerUserId, ownerUserId));
}
default List<CrmBusinessDO> selectListByCustomerId(Long customerId) {
return selectList(CrmBusinessDO::getCustomerId, customerId);
}
default PageResult<CrmBusinessDO> selectPage(CrmStatisticsFunnelReqVO pageVO) {
return selectPage(pageVO, new LambdaQueryWrapperX<CrmBusinessDO>()
.in(CrmBusinessDO::getOwnerUserId, pageVO.getUserIds())

View File

@ -195,6 +195,22 @@ public interface CrmBusinessService {
*/
List<CrmBusinessDO> getBusinessListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId);
/**
*
*
* @param customerId
* @return
*/
List<CrmBusinessDO> getBusinessListByCustomerId(Long customerId);
/**
*
*
* @param contactId
* @return
*/
List<CrmBusinessDO> getBusinessListByContact(Long contactId);
/**
*
*

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.crm.service.business;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
@ -15,6 +16,7 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessProductMapper;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
@ -194,9 +196,15 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
if (saveReqVO.getCustomerId() != null) {
customerService.validateCustomer(saveReqVO.getCustomerId());
}
// 校验联系人
// 校验联系人(且必须与商机属于同一客户,避免跨客户关联)
if (saveReqVO.getContactId() != null) {
contactService.validateContact(saveReqVO.getContactId());
if (saveReqVO.getCustomerId() != null) {
CrmContactDO contact = contactService.getContact(saveReqVO.getContactId());
if (ObjUtil.notEqual(saveReqVO.getCustomerId(), contact.getCustomerId())) {
throw exception(BUSINESS_CONTACT_CUSTOMER_NOT_MATCH);
}
}
}
// 校验负责人
if (saveReqVO.getOwnerUserId() != null) {
@ -377,6 +385,24 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
return businessMapper.selectListByCustomerIdOwnerUserId(customerId, ownerUserId);
}
@Override
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#customerId", level = CrmPermissionLevelEnum.READ)
public List<CrmBusinessDO> getBusinessListByCustomerId(Long customerId) {
return businessMapper.selectListByCustomerId(customerId);
}
@Override
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#contactId", level = CrmPermissionLevelEnum.READ)
public List<CrmBusinessDO> getBusinessListByContact(Long contactId) {
// 1. 查询关联的商机编号
List<CrmContactBusinessDO> contactBusinessList = contactBusinessService.getContactBusinessListByContactId(contactId);
if (CollUtil.isEmpty(contactBusinessList)) {
return ListUtil.empty();
}
// 2. 查询商机列表
return businessMapper.selectByIds(convertSet(contactBusinessList, CrmContactBusinessDO::getBusinessId));
}
@Override
public PageResult<CrmBusinessDO> getBusinessPageByDate(CrmStatisticsFunnelReqVO pageVO) {
return businessMapper.selectPage(pageVO);

View File

@ -169,4 +169,12 @@ public interface CrmContactService {
*/
List<CrmContactDO> getContactListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId);
/**
*
*
* @param customerId
* @return
*/
List<CrmContactDO> getContactListByCustomerId(Long customerId);
}

View File

@ -304,4 +304,10 @@ public class CrmContactServiceImpl implements CrmContactService {
return contactMapper.selectListByCustomerIdOwnerUserId(customerId, ownerUserId);
}
@Override
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#customerId", level = CrmPermissionLevelEnum.READ)
public List<CrmContactDO> getContactListByCustomerId(Long customerId) {
return contactMapper.selectListByCustomerId(customerId);
}
}

View File

@ -61,14 +61,14 @@ public class ErpSaleOrderController {
@PostMapping("/create")
@Operation(summary = "创建销售订单")
@PreAuthorize("@ss.hasPermission('erp:sale-out:create')")
@PreAuthorize("@ss.hasPermission('erp:sale-order:create')")
public CommonResult<Long> createSaleOrder(@Valid @RequestBody ErpSaleOrderSaveReqVO createReqVO) {
return success(saleOrderService.createSaleOrder(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新销售订单")
@PreAuthorize("@ss.hasPermission('erp:sale-out:update')")
@PreAuthorize("@ss.hasPermission('erp:sale-order:update')")
public CommonResult<Boolean> updateSaleOrder(@Valid @RequestBody ErpSaleOrderSaveReqVO updateReqVO) {
saleOrderService.updateSaleOrder(updateReqVO);
return success(true);
@ -76,7 +76,7 @@ public class ErpSaleOrderController {
@PutMapping("/update-status")
@Operation(summary = "更新销售订单的状态")
@PreAuthorize("@ss.hasPermission('erp:sale-out:update-status')")
@PreAuthorize("@ss.hasPermission('erp:sale-order:update-status')")
public CommonResult<Boolean> updateSaleOrderStatus(@RequestParam("id") Long id,
@RequestParam("status") Integer status) {
saleOrderService.updateSaleOrderStatus(id, status);
@ -86,7 +86,7 @@ public class ErpSaleOrderController {
@DeleteMapping("/delete")
@Operation(summary = "删除销售订单")
@Parameter(name = "ids", description = "编号数组", required = true)
@PreAuthorize("@ss.hasPermission('erp:sale-out:delete')")
@PreAuthorize("@ss.hasPermission('erp:sale-order:delete')")
public CommonResult<Boolean> deleteSaleOrder(@RequestParam("ids") List<Long> ids) {
saleOrderService.deleteSaleOrder(ids);
return success(true);
@ -95,7 +95,7 @@ public class ErpSaleOrderController {
@GetMapping("/get")
@Operation(summary = "获得销售订单")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('erp:sale-out:query')")
@PreAuthorize("@ss.hasPermission('erp:sale-order:query')")
public CommonResult<ErpSaleOrderRespVO> getSaleOrder(@RequestParam("id") Long id) {
ErpSaleOrderDO saleOrder = saleOrderService.getSaleOrder(id);
if (saleOrder == null) {
@ -115,7 +115,7 @@ public class ErpSaleOrderController {
@GetMapping("/page")
@Operation(summary = "获得销售订单分页")
@PreAuthorize("@ss.hasPermission('erp:sale-out:query')")
@PreAuthorize("@ss.hasPermission('erp:sale-order:query')")
public CommonResult<PageResult<ErpSaleOrderRespVO>> getSaleOrderPage(@Valid ErpSaleOrderPageReqVO pageReqVO) {
PageResult<ErpSaleOrderDO> pageResult = saleOrderService.getSaleOrderPage(pageReqVO);
return success(buildSaleOrderVOPageResult(pageResult));
@ -123,7 +123,7 @@ public class ErpSaleOrderController {
@GetMapping("/export-excel")
@Operation(summary = "导出销售订单 Excel")
@PreAuthorize("@ss.hasPermission('erp:sale-out:export')")
@PreAuthorize("@ss.hasPermission('erp:sale-order:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportSaleOrderExcel(@Valid ErpSaleOrderPageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
@ -161,4 +161,4 @@ public class ErpSaleOrderController {
});
}
}
}

View File

@ -40,49 +40,13 @@
/>
</view>
#elseif ($column.htmlType == "datetime" && $listOperationCondition == "BETWEEN")
#set ($AttrName = $javaField.substring(0,1).toUpperCase() + ${javaField.substring(1)})
<view class="yd-search-form-item">
<view class="yd-search-form-label">
${comment}
</view>
<view class="yd-search-form-date-range-container">
<view @click="visible${AttrName}[0] = true">
<view class="yd-search-form-date-range-picker">
{{ formatDate(formData.${javaField}?.[0]) || '开始日期' }}
</view>
</view>
-
<view @click="visible${AttrName}[1] = true">
<view class="yd-search-form-date-range-picker">
{{ formatDate(formData.${javaField}?.[1]) || '结束日期' }}
</view>
</view>
</view>
<wd-datetime-picker-view v-if="visible${AttrName}[0]" v-model="temp${AttrName}[0]" type="date" />
<view v-if="visible${AttrName}[0]" class="yd-search-form-date-range-actions">
<wd-button size="small" plain @click="visible${AttrName}[0] = false">
取消
</wd-button>
<wd-button size="small" type="primary" @click="handle${AttrName}0Confirm">
确定
</wd-button>
</view>
<wd-datetime-picker-view v-if="visible${AttrName}[1]" v-model="temp${AttrName}[1]" type="date" />
<view v-if="visible${AttrName}[1]" class="yd-search-form-date-range-actions">
<wd-button size="small" plain @click="visible${AttrName}[1] = false">
取消
</wd-button>
<wd-button size="small" type="primary" @click="handle${AttrName}1Confirm">
确定
</wd-button>
</view>
</view>
<yd-search-date-range v-model="formData.${javaField}" label="${comment}" />
#elseif (($column.htmlType == "select" || $column.htmlType == "radio") && $dictType && "" != $dictType)
<view class="yd-search-form-item">
<view class="yd-search-form-label">
${comment}
</view>
<wd-radio-group v-model="formData.${javaField}" shape="button">
<wd-radio-group v-model="formData.${javaField}" type="button">
<wd-radio :value="-1">
全部
</wd-radio>
@ -236,29 +200,6 @@ const placeholder = computed(() => {
return conditions.length > 0 ? conditions.join(' | ') : '搜索${table.classComment}'
})
#if ($hasDateTimeBetween == 1)
#foreach($column in $columns)
#if ($column.listOperation && $column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN")
#set ($javaField = $column.javaField)
#set ($AttrName = $javaField.substring(0,1).toUpperCase() + ${javaField.substring(1)})
const visible${AttrName} = ref<[boolean, boolean]>([false, false]) // ${column.columnComment}选择器状态
const temp${AttrName} = ref<[number, number]>([Date.now(), Date.now()]) // ${column.columnComment}临时值
/** 确认${column.columnComment}开始日期 */
function handle${AttrName}0Confirm() {
formData.${javaField} = [temp${AttrName}.value[0], formData.${javaField}?.[1]]
visible${AttrName}.value[0] = false
}
/** 确认${column.columnComment}结束日期 */
function handle${AttrName}1Confirm() {
formData.${javaField} = [formData.${javaField}?.[0], temp${AttrName}.value[1]]
visible${AttrName}.value[1] = false
}
#end
#end
#end
/** 搜索按钮操作 */
function handleSearch() {
visible.value = false

View File

@ -64,10 +64,10 @@
import type { ${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
import { onUnload } from '@dcloudio/uni-app'
import { onMounted, ref } from 'vue'
import { useToast } from 'wot-design-uni'
import { useToast } from '@wot-ui/ui/components/wd-toast'
import { delete${simpleClassName}, get${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
import { useAccess } from '@/hooks/useAccess'
import { navigateBackPlus } from '@/utils'
import { delay, navigateBackPlus } from '@/utils'
#if ($hasDict == 1)
import { DICT_TYPE } from '@/utils/constants'
#end
@ -133,9 +133,7 @@ function handleDelete() {
await delete${simpleClassName}(props.id)
toast.success('删除成功')
uni.$emit('${table.moduleName}:${table.businessName}:reload')
setTimeout(() => {
handleBack()
}, 500)
delay(handleBack)
} finally {
deleting.value = false
}

View File

@ -9,7 +9,7 @@
<!-- 表单区域 -->
<view>
<wd-form ref="formRef" :model="formData" :rules="formRules">
<wd-form ref="formRef" :model="formData" :schema="formSchema">
<wd-cell-group border>
#foreach($column in $columns)
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey)
@ -25,10 +25,23 @@
#elseif ($javaType == "Boolean")
#set ($dictMethod = "getBoolDictOptions")
#end
## 优先判断是否有字典,有字典则使用 radio-group
#if (($column.htmlType == "select" || $column.htmlType == "radio") && $dictType && "" != $dictType)
<wd-cell title="${comment}" title-width="180rpx" prop="${javaField}" center>
<wd-radio-group v-model="formData.${javaField}" shape="button">
## 下拉选择(字典)→ yd-form-picker内部按 dictType 解析选项,适合选项较多
#if ($column.htmlType == "select" && $dictType && "" != $dictType)
<yd-form-picker
v-model="formData.${javaField}"
label="${comment}"
label-width="180rpx"
prop="${javaField}"
:dict-type="DICT_TYPE.${dictType.toUpperCase()}"
#if ($javaType == "String")
dict-kind="str"
#end
placeholder="请选择${comment}"
/>
## 单选(字典)→ radio-group适合选项较少
#elseif ($column.htmlType == "radio" && $dictType && "" != $dictType)
<wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}" center>
<wd-radio-group v-model="formData.${javaField}" type="button">
<wd-radio
v-for="dict in $dictMethod(DICT_TYPE.${dictType.toUpperCase()})"
:key="dict.value"
@ -37,54 +50,78 @@
{{ dict.label }}
</wd-radio>
</wd-radio-group>
</wd-cell>
</wd-form-item>
## 数字类型(无字典)
#elseif (${javaType.toLowerCase()} == "long" || ${javaType.toLowerCase()} == "integer" || ${javaType.toLowerCase()} == "short" || ${javaType.toLowerCase()} == "double" || ${javaType.toLowerCase()} == "bigdecimal" || ${javaType.toLowerCase()} == "byte")
<wd-cell title="${comment}" title-width="180rpx" prop="${javaField}" center>
<wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}">
<wd-input-number
v-model="formData.${javaField}"
#if (${javaType.toLowerCase()} == "double" || ${javaType.toLowerCase()} == "bigdecimal")
:precision="2"
input-type="number"
#end
:min="0"
/>
</wd-cell>
</wd-form-item>
## 布尔类型
#elseif (${javaType.toLowerCase()} == "boolean")
<wd-cell title="${comment}" title-width="180rpx" prop="${javaField}" center>
<wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}" center>
<wd-switch v-model="formData.${javaField}" />
</wd-cell>
</wd-form-item>
## 日期时间类型
#elseif (${javaType.toLowerCase()} == "date" || ${javaType.toLowerCase()} == "localdate" || ${javaType.toLowerCase()} == "localdatetime")
#set ($pickerType = "date")
#if (${javaType.toLowerCase()} == "localdatetime")
#set ($pickerType = "datetime")
#end
<wd-form-item
title="${comment}"
title-width="180rpx"
prop="${javaField}"
is-link
#if ($pickerType == "datetime")
:value="formatDateTime(formData.${javaField})"
#else
:value="formatDate(formData.${javaField})"
#end
placeholder="请选择${comment}"
@click="${javaField}Visible = true"
/>
<wd-datetime-picker
v-model="formData.${javaField}"
v-model:visible="${javaField}Visible"
type="${pickerType}"
label="${comment}"
label-width="180rpx"
prop="${javaField}"
/>
## 文本域
#elseif ($column.htmlType == "textarea")
<wd-textarea
v-model="formData.${javaField}"
label="${comment}"
label-width="180rpx"
placeholder="请输入${comment}"
:maxlength="200"
show-word-limit
clearable
/>
<wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}">
<wd-textarea
v-model="formData.${javaField}"
clearable
placeholder="请输入${comment}"
:maxlength="200"
show-word-limit
/>
</wd-form-item>
## 图片上传 → yd-upload-img包 wd-form-item 才能注册到 wd-form 做校验/错误展示)
#elseif ($column.htmlType == "imageUpload")
<wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}">
<yd-upload-img v-model="formData.${javaField}" directory="${table.moduleName}/${table.businessName}" />
</wd-form-item>
## 文件上传 → yd-upload-file
#elseif ($column.htmlType == "fileUpload")
<wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}">
<yd-upload-file v-model="formData.${javaField}" directory="${table.moduleName}/${table.businessName}" />
</wd-form-item>
## 默认:文本输入
#else
<wd-input
v-model="formData.${javaField}"
label="${comment}"
label-width="180rpx"
prop="${javaField}"
clearable
placeholder="请输入${comment}"
/>
<wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}">
<wd-input
v-model="formData.${javaField}"
clearable
placeholder="请输入${comment}"
/>
</wd-form-item>
#end
#end
#end
@ -107,7 +144,7 @@
</template>
<script lang="ts" setup>
import type { FormInstance } from 'wot-design-uni/components/wd-form/types'
import type { FormInstance } from '@wot-ui/ui/components/wd-form/types'
#set ($primaryJavaType = $primaryColumn.javaType.toLowerCase())
#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
#set ($primaryTsType = "number")
@ -115,6 +152,8 @@ import type { FormInstance } from 'wot-design-uni/components/wd-form/types'
#set ($primaryTsType = "string")
#end
#set ($hasDict = 0)
#set ($hasDate = 0)
#set ($hasDateTime = 0)
#set ($hasGetDictOptions = 0)
#set ($hasGetIntDictOptions = 0)
#set ($hasGetStrDictOptions = 0)
@ -124,22 +163,30 @@ import type { FormInstance } from 'wot-design-uni/components/wd-form/types'
&& ($column.htmlType == "select" || $column.htmlType == "radio")
&& $column.dictType && "" != $column.dictType)
#set ($hasDict = 1)
#if ($column.javaType == "Integer" || $column.javaType == "Long" || $column.javaType == "Byte" || $column.javaType == "Short")
#if ($column.htmlType == "radio" && ($column.javaType == "Integer" || $column.javaType == "Long" || $column.javaType == "Byte" || $column.javaType == "Short"))
#set ($hasGetIntDictOptions = 1)
#elseif ($column.javaType == "String")
#elseif ($column.htmlType == "radio" && $column.javaType == "String")
#set ($hasGetStrDictOptions = 1)
#elseif ($column.javaType == "Boolean")
#elseif ($column.htmlType == "radio" && $column.javaType == "Boolean")
#set ($hasGetBoolDictOptions = 1)
#else
#elseif ($column.htmlType == "radio")
#set ($hasGetDictOptions = 1)
#end
#end
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey
&& (${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate"))
#set ($hasDate = 1)
#end
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey
&& ${column.javaType.toLowerCase()} == "localdatetime")
#set ($hasDateTime = 1)
#end
#end
import type { ${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
import { computed, onMounted, ref } from 'vue'
import { useToast } from 'wot-design-uni'
import { useToast } from '@wot-ui/ui/components/wd-toast'
import { create${simpleClassName}, get${simpleClassName}, update${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
#if ($hasDict == 1)
#if ($hasGetDictOptions == 1 || $hasGetIntDictOptions == 1 || $hasGetStrDictOptions == 1 || $hasGetBoolDictOptions == 1)
#set ($dictImportNames = "")
#if ($hasGetDictOptions == 1)
#set ($dictImportNames = "${dictImportNames}getDictOptions, ")
@ -157,10 +204,23 @@ import { create${simpleClassName}, get${simpleClassName}, update${simpleClassNam
#set ($dictImportNames = $dictImportNames.substring(0, $dictImportNames.length() - 1))
import { $dictImportNames } from '@/hooks/useDict'
#end
import { navigateBackPlus } from '@/utils'
import { delay, navigateBackPlus } from '@/utils'
#if ($hasDict == 1)
import { DICT_TYPE } from '@/utils/constants'
#end
#if ($hasDate == 1 || $hasDateTime == 1)
#set ($dateImportNames = "")
#if ($hasDate == 1)
#set ($dateImportNames = "${dateImportNames}formatDate, ")
#end
#if ($hasDateTime == 1)
#set ($dateImportNames = "${dateImportNames}formatDateTime, ")
#end
#set ($dateImportNames = $dateImportNames.trim())
#set ($dateImportNames = $dateImportNames.substring(0, $dateImportNames.length() - 1))
import { $dateImportNames } from '@/utils/date'
#end
import { createFormSchema } from '@/utils/wot'
const props = defineProps<{
id?: ${primaryTsType} | any
@ -196,7 +256,12 @@ const formData = ref<${simpleClassName}>({
#end
#end
}) // 表单数据
const formRules = {
#foreach($column in $columns)
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && (${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate" || ${column.javaType.toLowerCase()} == "localdatetime"))
const ${column.javaField}Visible = ref(false) // ${column.columnComment}选择器状态
#end
#end
const formSchema = createFormSchema({
#foreach($column in $columns)
#set ($javaFieldLower = $column.javaField.toLowerCase())
#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !$column.primaryKey
@ -204,7 +269,7 @@ const formRules = {
${column.javaField}: [{ required: true, message: '${column.columnComment}不能为空' }],
#end
#end
} // 表单校验规则
}) // 表单校验规则
const formRef = ref<FormInstance>() // 表单组件引用
/** 返回上一页 */
@ -237,9 +302,7 @@ async function handleSubmit() {
toast.success('新增成功')
}
uni.$emit('${table.moduleName}:${table.businessName}:reload')
setTimeout(() => {
handleBack()
}, 500)
delay(handleBack)
} finally {
formLoading.value = false
}

View File

@ -152,10 +152,21 @@ public abstract class CodegenEngineAbstractTest extends BaseMockitoUnitTest {
protected void writeResult(Map<String, String> result, String basePath) {
// 写入文件内容
List<Map<String, String>> asserts = new ArrayList<>();
Set<String> usedContentPaths = new LinkedHashSet<>();
result.forEach((filePath, fileContent) -> {
String lastFilePath = StrUtil.subAfter(filePath, '/', true);
String contentPath = StrUtil.subAfter(lastFilePath, '.', true)
+ '/' + StrUtil.subBefore(lastFilePath, '.', true);
String ext = StrUtil.subAfter(lastFilePath, '.', true);
String name = StrUtil.subBefore(lastFilePath, '.', true);
String contentPath = ext + '/' + name;
// 同名文件(如 index.vue 同时出现在 列表/form/detail会撞名撞名时前缀补上级目录区分仍撞则加序号兜底避免快照互相覆盖
if (usedContentPaths.contains(contentPath)) {
String parentDir = StrUtil.subAfter(StrUtil.subBefore(filePath, '/' + lastFilePath, true), '/', true);
contentPath = ext + '/' + parentDir + '/' + name;
for (int i = 2; usedContentPaths.contains(contentPath); i++) {
contentPath = ext + '/' + parentDir + '/' + name + '-' + i;
}
}
usedContentPaths.add(contentPath);
asserts.add(MapUtil.<String, String>builder().put("filePath", filePath)
.put("contentPath", contentPath).build());
FileUtil.writeUtf8String(fileContent, basePath + "/" + contentPath);

View File

@ -51,10 +51,10 @@
import type { Student } from '@/api/infra/demo'
import { onUnload } from '@dcloudio/uni-app'
import { onMounted, ref } from 'vue'
import { useToast } from 'wot-design-uni'
import { useToast } from '@wot-ui/ui/components/wd-toast'
import { deleteStudent, getStudent } from '@/api/infra/demo'
import { useAccess } from '@/hooks/useAccess'
import { navigateBackPlus } from '@/utils'
import { delay, navigateBackPlus } from '@/utils'
import { DICT_TYPE } from '@/utils/constants'
import { formatDateTime } from '@/utils/date'
@ -116,9 +116,7 @@ function handleDelete() {
await deleteStudent(props.id)
toast.success('删除成功')
uni.$emit('infra:demo:reload')
setTimeout(() => {
handleBack()
}, 500)
delay(handleBack)
} finally {
deleting.value = false
}

View File

@ -9,45 +9,48 @@
<!-- 表单区域 -->
<view>
<wd-form ref="formRef" :model="formData" :rules="formRules">
<wd-form ref="formRef" :model="formData" :schema="formSchema">
<wd-cell-group border>
<wd-input
v-model="formData.name"
label="名字"
label-width="180rpx"
prop="name"
clearable
placeholder="请输入名字"
/>
<wd-textarea
v-model="formData.description"
label="简介"
label-width="180rpx"
placeholder="请输入简介"
:maxlength="200"
show-word-limit
clearable
/>
<wd-datetime-picker
v-model="formData.birthday"
type="datetime"
label="出生日期"
label-width="180rpx"
<wd-form-item title="名字" title-width="180rpx" prop="name">
<wd-input
v-model="formData.name"
clearable
placeholder="请输入名字"
/>
</wd-form-item>
<wd-form-item title="简介" title-width="180rpx" prop="description">
<wd-textarea
v-model="formData.description"
clearable
placeholder="请输入简介"
:maxlength="200"
show-word-limit
/>
</wd-form-item>
<wd-form-item
title="出生日期"
title-width="180rpx"
prop="birthday"
is-link
:value="formatDateTime(formData.birthday)"
placeholder="请选择出生日期"
@click="birthdayVisible = true"
/>
<wd-cell title="性别" title-width="180rpx" prop="sex" center>
<wd-radio-group v-model="formData.sex" shape="button">
<wd-radio
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
:key="dict.value"
:value="dict.value"
>
{{ dict.label }}
</wd-radio>
</wd-radio-group>
</wd-cell>
<wd-cell title="是否有效" title-width="180rpx" prop="enabled" center>
<wd-radio-group v-model="formData.enabled" shape="button">
<wd-datetime-picker
v-model="formData.birthday"
v-model:visible="birthdayVisible"
type="datetime"
/>
<yd-form-picker
v-model="formData.sex"
label="性别"
label-width="180rpx"
prop="sex"
:dict-type="DICT_TYPE.SYSTEM_USER_SEX"
placeholder="请选择性别"
/>
<wd-form-item title="是否有效" title-width="180rpx" prop="enabled" center>
<wd-radio-group v-model="formData.enabled" type="button">
<wd-radio
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
:key="dict.value"
@ -56,31 +59,20 @@
{{ dict.label }}
</wd-radio>
</wd-radio-group>
</wd-cell>
<wd-input
v-model="formData.avatar"
label="头像"
label-width="180rpx"
prop="avatar"
clearable
placeholder="请输入头像"
/>
<wd-input
v-model="formData.video"
label="附件"
label-width="180rpx"
prop="video"
clearable
placeholder="请输入附件"
/>
<wd-input
v-model="formData.memo"
label="备注"
label-width="180rpx"
prop="memo"
clearable
placeholder="请输入备注"
/>
</wd-form-item>
<wd-form-item title="头像" title-width="180rpx" prop="avatar">
<yd-upload-img v-model="formData.avatar" directory="infra/demo" />
</wd-form-item>
<wd-form-item title="附件" title-width="180rpx" prop="video">
<yd-upload-file v-model="formData.video" directory="infra/demo" />
</wd-form-item>
<wd-form-item title="备注" title-width="180rpx" prop="memo">
<wd-input
v-model="formData.memo"
clearable
placeholder="请输入备注"
/>
</wd-form-item>
</wd-cell-group>
</wd-form>
</view>
@ -100,14 +92,16 @@
</template>
<script lang="ts" setup>
import type { FormInstance } from 'wot-design-uni/components/wd-form/types'
import type { FormInstance } from '@wot-ui/ui/components/wd-form/types'
import type { Student } from '@/api/infra/demo'
import { computed, onMounted, ref } from 'vue'
import { useToast } from 'wot-design-uni'
import { useToast } from '@wot-ui/ui/components/wd-toast'
import { createStudent, getStudent, updateStudent } from '@/api/infra/demo'
import { getIntDictOptions, getBoolDictOptions } from '@/hooks/useDict'
import { navigateBackPlus } from '@/utils'
import { getBoolDictOptions } from '@/hooks/useDict'
import { delay, navigateBackPlus } from '@/utils'
import { DICT_TYPE } from '@/utils/constants'
import { formatDateTime } from '@/utils/date'
import { createFormSchema } from '@/utils/wot'
const props = defineProps<{
id?: number | any
@ -134,7 +128,8 @@ const formData = ref<Student>({
video: '',
memo: '',
}) // 表单数据
const formRules = {
const birthdayVisible = ref(false) // 出生日期选择器状态
const formSchema = createFormSchema({
name: [{ required: true, message: '名字不能为空' }],
description: [{ required: true, message: '简介不能为空' }],
birthday: [{ required: true, message: '出生日期不能为空' }],
@ -143,7 +138,7 @@ const formRules = {
avatar: [{ required: true, message: '头像不能为空' }],
video: [{ required: true, message: '附件不能为空' }],
memo: [{ required: true, message: '备注不能为空' }],
} // 表单校验规则
}) // 表单校验规则
const formRef = ref<FormInstance>() // 表单组件引用
/** 返回上一页 */
@ -176,9 +171,7 @@ async function handleSubmit() {
toast.success('新增成功')
}
uni.$emit('infra:demo:reload')
setTimeout(() => {
handleBack()
}, 500)
delay(handleBack)
} finally {
formLoading.value = false
}

View File

@ -31,7 +31,7 @@
<view class="yd-search-form-label">
性别
</view>
<wd-radio-group v-model="formData.sex" shape="button">
<wd-radio-group v-model="formData.sex" type="button">
<wd-radio :value="-1">
全部
</wd-radio>
@ -48,7 +48,7 @@
<view class="yd-search-form-label">
是否有效
</view>
<wd-radio-group v-model="formData.enabled" shape="button">
<wd-radio-group v-model="formData.enabled" type="button">
<wd-radio :value="-1">
全部
</wd-radio>
@ -61,42 +61,7 @@
</wd-radio>
</wd-radio-group>
</view>
<view class="yd-search-form-item">
<view class="yd-search-form-label">
创建时间
</view>
<view class="yd-search-form-date-range-container">
<view @click="visibleCreateTime[0] = true">
<view class="yd-search-form-date-range-picker">
{{ formatDate(formData.createTime?.[0]) || '开始日期' }}
</view>
</view>
-
<view @click="visibleCreateTime[1] = true">
<view class="yd-search-form-date-range-picker">
{{ formatDate(formData.createTime?.[1]) || '结束日期' }}
</view>
</view>
</view>
<wd-datetime-picker-view v-if="visibleCreateTime[0]" v-model="tempCreateTime[0]" type="date" />
<view v-if="visibleCreateTime[0]" class="yd-search-form-date-range-actions">
<wd-button size="small" plain @click="visibleCreateTime[0] = false">
取消
</wd-button>
<wd-button size="small" type="primary" @click="handleCreateTime0Confirm">
确定
</wd-button>
</view>
<wd-datetime-picker-view v-if="visibleCreateTime[1]" v-model="tempCreateTime[1]" type="date" />
<view v-if="visibleCreateTime[1]" class="yd-search-form-date-range-actions">
<wd-button size="small" plain @click="visibleCreateTime[1] = false">
取消
</wd-button>
<wd-button size="small" type="primary" @click="handleCreateTime1Confirm">
确定
</wd-button>
</view>
</view>
<yd-search-date-range v-model="formData.createTime" label="创建时间" />
<view class="yd-search-form-actions">
<wd-button class="flex-1" plain @click="handleReset">
重置
@ -151,21 +116,6 @@ const placeholder = computed(() => {
return conditions.length > 0 ? conditions.join(' | ') : '搜索学生'
})
const visibleCreateTime = ref<[boolean, boolean]>([false, false]) // 创建时间选择器状态
const tempCreateTime = ref<[number, number]>([Date.now(), Date.now()]) // 创建时间临时值
/** 确认创建时间开始日期 */
function handleCreateTime0Confirm() {
formData.createTime = [tempCreateTime.value[0], formData.createTime?.[1]]
visibleCreateTime.value[0] = false
}
/** 确认创建时间结束日期 */
function handleCreateTime1Confirm() {
formData.createTime = [formData.createTime?.[0], tempCreateTime.value[1]]
visibleCreateTime.value[1] = false
}
/** 搜索按钮操作 */
function handleSearch() {
visible.value = false

View File

@ -1,9 +1,12 @@
package cn.iocoder.yudao.module.iot.framework.tdengine.core;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Locale;
/**
* TDEngine
*/
@ -61,4 +64,15 @@ public class TDengineTableField {
this.type = type;
}
/**
* TDengine
* PT -> ptPfT -> pf_t
*/
public static String buildFieldName(String field) {
if (StrUtil.isBlank(field)) {
return field;
}
return StrUtil.toUnderlineCase(field).toLowerCase(Locale.ROOT);
}
}

View File

@ -118,7 +118,7 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService {
private List<TDengineTableField> buildTableFieldList(List<IotThingModelDO> thingModels) {
return convertList(thingModels, thingModel -> {
TDengineTableField field = new TDengineTableField(
StrUtil.toUnderlineCase(thingModel.getIdentifier()), // TDengine 字段默认都是小写
TDengineTableField.buildFieldName(thingModel.getIdentifier()),
TYPE_MAPPING.get(thingModel.getProperty().getDataType()));
String dataType = thingModel.getProperty().getDataType();
if (Objects.equals(dataType, IotDataSpecsDataTypeEnum.TEXT.getDataType())) {

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.iot.service.rule.scene;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.text.CharPool;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition.IotCurrentTimeConditionMatcher;
import cn.iocoder.yudao.module.iot.service.rule.scene.timer.IotTimerConditionEvaluator;
@ -11,7 +12,6 @@ import lombok.extern.slf4j.Slf4j;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List;
@ -73,7 +73,7 @@ public class IotSceneRuleTimeHelper {
LocalDateTime now = LocalDateTime.now();
if (isDateTimeOperator(operatorEnum)) {
// 日期时间匹配(时间戳,秒级)
long currentTimestamp = now.atZone(ZoneId.systemDefault()).toEpochSecond();
long currentTimestamp = LocalDateTimeUtils.toEpochSecond(now);
return matchDateTime(currentTimestamp, operatorEnum, param);
} else {
// 当日时间匹配HH:mm:ss

View File

@ -51,7 +51,7 @@
TAGS ('${device.id}')
(ts, report_time,
<foreach item="key" collection="properties.keys" separator=",">
${@cn.hutool.core.util.StrUtil@toUnderlineCase(key)}
${@cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField@buildFieldName(key)}
</foreach>
)
VALUES
@ -68,12 +68,12 @@
<select id="selectListByHistory"
resultType="cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyRespVO">
SELECT ${@cn.hutool.core.util.StrUtil@toUnderlineCase(reqVO.identifier)} AS `value`, ts AS update_time
SELECT ${@cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField@buildFieldName(reqVO.identifier)} AS `value`, ts AS update_time
FROM device_property_${reqVO.deviceId}
WHERE ${@cn.hutool.core.util.StrUtil@toUnderlineCase(reqVO.identifier)} IS NOT NULL
WHERE ${@cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField@buildFieldName(reqVO.identifier)} IS NOT NULL
AND ts BETWEEN ${@cn.hutool.core.date.LocalDateTimeUtil@toEpochMilli(reqVO.times[0])}
AND ${@cn.hutool.core.date.LocalDateTimeUtil@toEpochMilli(reqVO.times[1])}
ORDER BY ts DESC
</select>
</mapper>
</mapper>

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.iot.framework.tdengine.core;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
public class TDengineTableFieldTest {
@Test
public void testBuildFieldName() {
assertEquals("ua", TDengineTableField.buildFieldName("Ua"));
assertEquals("pf_t", TDengineTableField.buildFieldName("PfT"));
assertEquals("pt", TDengineTableField.buildFieldName("PT"));
assertEquals("pa", TDengineTableField.buildFieldName("PA"));
assertEquals("geo_location", TDengineTableField.buildFieldName("GeoLocation"));
assertEquals("light_status", TDengineTableField.buildFieldName("LightStatus"));
}
@Test
public void testBuildFieldName_blank() {
assertNull(TDengineTableField.buildFieldName(null));
assertEquals("", TDengineTableField.buildFieldName(""));
assertEquals(" ", TDengineTableField.buildFieldName(" "));
}
}

View File

@ -5,11 +5,15 @@ import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDevicePropertyDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelProperty;
import cn.iocoder.yudao.module.iot.dal.redis.device.DevicePropertyRedisDAO;
import cn.iocoder.yudao.module.iot.dal.tdengine.IotDevicePropertyMapper;
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum;
import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
import cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField;
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
@ -17,8 +21,12 @@ import org.mockito.InjectMocks;
import org.mockito.Mock;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static java.util.Collections.singletonList;
import static org.junit.jupiter.api.Assertions.*;
@ -38,6 +46,8 @@ public class IotDevicePropertyServiceImplTest extends BaseMockitoUnitTest {
@Mock
private IotThingModelService thingModelService;
@Mock
private IotProductService productService;
@Mock
private IotDevicePropertyMapper devicePropertyMapper;
@Mock
private DevicePropertyRedisDAO deviceDataRedisDAO;
@ -100,12 +110,15 @@ public class IotDevicePropertyServiceImplTest extends BaseMockitoUnitTest {
params.put("Temperature", "abc");
IotDeviceMessage message = buildMessage(params);
// mock 方法
when(thingModelService.getThingModelListByProductIdFromCache(device.getProductId()))
.thenReturn(singletonList(temperature));
when(thingModelService.convertThingModelPropertyValue(temperature, "abc")).thenReturn(null);
// 调用,并断言:不会抛出异常
assertDoesNotThrow(() -> service.saveDeviceProperty(device, message));
// 断言:没有合法属性,不会写入 TDengine 与 Redis
verify(devicePropertyMapper, never()).insert(any(), any(), anyLong(), anyLong());
verify(deviceDataRedisDAO, never()).putAll(anyLong(), any());
}
@ -119,11 +132,14 @@ public class IotDevicePropertyServiceImplTest extends BaseMockitoUnitTest {
params.put("Temperature", null);
IotDeviceMessage message = buildMessage(params);
// mock 方法
when(thingModelService.getThingModelListByProductIdFromCache(device.getProductId()))
.thenReturn(singletonList(thingModel));
// 调用,并断言:不会抛出异常
assertDoesNotThrow(() -> service.saveDeviceProperty(device, message));
// 断言:跳过空值,不会转换属性值,也不会写入 TDengine 与 Redis
verify(thingModelService, never()).convertThingModelPropertyValue(any(), any());
verify(devicePropertyMapper, never()).insert(any(), any(), anyLong(), anyLong());
verify(deviceDataRedisDAO, never()).putAll(anyLong(), any());
@ -139,17 +155,48 @@ public class IotDevicePropertyServiceImplTest extends BaseMockitoUnitTest {
params.put("PowerSwitch", true);
IotDeviceMessage message = buildMessage(params);
// mock 方法
when(thingModelService.getThingModelListByProductIdFromCache(device.getProductId()))
.thenReturn(singletonList(thingModel));
when(thingModelService.convertThingModelPropertyValue(thingModel, true)).thenReturn((byte) 1);
// 调用,并断言:非字符串 key 不影响其它合法属性
assertDoesNotThrow(() -> service.saveDeviceProperty(device, message));
// 断言:只写入合法属性
Map<String, Object> dbProperties = captureMapperInsertProperties();
assertEquals(1, dbProperties.size());
assertEquals((byte) 1, dbProperties.get("PowerSwitch"));
}
@Test
public void testDefineDevicePropertyData_fieldNameToLowerCase() {
// 准备参数:全大写缩写和驼峰缩写都需要转换为 TDengine 实际的小写字段名
Long productId = 2L;
IotProductDO product = IotProductDO.builder().id(productId).build();
List<IotThingModelDO> thingModels = Arrays.asList(
buildThingModel("Ua", IotDataSpecsDataTypeEnum.FLOAT.getDataType()),
buildThingModel("PfT", IotDataSpecsDataTypeEnum.FLOAT.getDataType()),
buildThingModel("PT", IotDataSpecsDataTypeEnum.FLOAT.getDataType()),
buildThingModel("PA", IotDataSpecsDataTypeEnum.FLOAT.getDataType()));
thingModels.forEach(thingModel -> thingModel.setType(IotThingModelTypeEnum.PROPERTY.getType()));
// mock 方法
when(productService.validateProductExists(productId)).thenReturn(product);
when(thingModelService.getThingModelListByProductId(productId)).thenReturn(thingModels);
when(devicePropertyMapper.getProductPropertySTableFieldList(productId)).thenReturn(Collections.emptyList());
// 调用
service.defineDevicePropertyData(productId);
// 断言:字段名统一为小写下划线,避免 PT 和数据库中的 pt 被误判为不同字段
ArgumentCaptor<List<TDengineTableField>> captor = ArgumentCaptor.forClass(List.class);
verify(devicePropertyMapper).createProductPropertySTable(eq(productId), captor.capture());
assertEquals(Arrays.asList("ua", "pf_t", "pt", "pa"), captor.getValue().stream()
.map(TDengineTableField::getField)
.collect(Collectors.toList()));
}
// ========== 辅助方法 ==========
/**

View File

@ -7,6 +7,7 @@ import cn.iocoder.yudao.module.product.controller.admin.comment.vo.*;
import cn.iocoder.yudao.module.product.dal.dataobject.comment.ProductCommentDO;
import cn.iocoder.yudao.module.product.service.comment.ProductCommentService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
@ -34,6 +35,15 @@ public class ProductCommentController {
return success(BeanUtils.toBean(pageResult, ProductCommentRespVO.class));
}
@GetMapping("/get")
@Operation(summary = "获得商品评价")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('product:comment:query')")
public CommonResult<ProductCommentRespVO> getComment(@RequestParam("id") Long id) {
ProductCommentDO comment = productCommentService.getComment(id);
return success(BeanUtils.toBean(comment, ProductCommentRespVO.class));
}
@PutMapping("/update-visible")
@Operation(summary = "显示 / 隐藏评论")
@PreAuthorize("@ss.hasPermission('product:comment:update')")

View File

@ -118,10 +118,10 @@ public class ProductSpuController {
}
@GetMapping("/get-count")
@Operation(summary = "获得商品 SPU 分页 tab count")
@Operation(summary = "获得商品 SPU 分页 tab count(支持按 name/categoryId/createTime 筛选)")
@PreAuthorize("@ss.hasPermission('product:spu:query')")
public CommonResult<Map<Integer, Long>> getSpuCount() {
return success(productSpuService.getTabsCount());
public CommonResult<Map<Integer, Long>> getSpuCount(ProductSpuPageReqVO reqVO) {
return success(productSpuService.getTabsCount(reqVO));
}
@GetMapping("/export-excel")

View File

@ -146,6 +146,22 @@ public interface ProductSpuMapper extends BaseMapperX<ProductSpuDO> {
}
}
/**
* + Tab SPU tab count
*
* @param reqVO name/categoryId/createTime
* @param tabType Tab
* @return
*/
default Long selectCountByTab(ProductSpuPageReqVO reqVO, Integer tabType) {
LambdaQueryWrapperX<ProductSpuDO> queryWrapper = new LambdaQueryWrapperX<ProductSpuDO>()
.likeIfPresent(ProductSpuDO::getName, reqVO.getName())
.eqIfPresent(ProductSpuDO::getCategoryId, reqVO.getCategoryId())
.betweenIfPresent(ProductSpuDO::getCreateTime, reqVO.getCreateTime());
appendTabQuery(tabType, queryWrapper);
return selectCount(queryWrapper);
}
/**
* SPU
*

View File

@ -52,6 +52,14 @@ public interface ProductCommentService {
*/
void replyComment(ProductCommentReplyReqVO replyVO, Long userId);
/**
*
*
* @param id
* @return
*/
ProductCommentDO getComment(Long id);
/**
*
*

View File

@ -139,6 +139,11 @@ public class ProductCommentServiceImpl implements ProductCommentService {
return productCommentMapper.selectPage(pageVO, visible);
}
@Override
public ProductCommentDO getComment(Long id) {
return validateCommentExists(id);
}
@Override
public PageResult<ProductCommentDO> getCommentPage(ProductCommentPageReqVO pageReqVO) {
return productCommentMapper.selectPage(pageReqVO);

View File

@ -118,11 +118,12 @@ public interface ProductSpuService {
void updateSpuStatus(ProductSpuUpdateStatusReqVO updateReqVO);
/**
* SPU Count
* SPU Count name/categoryId/createTime
*
* @param reqVO
* @return Count
*/
Map<Integer, Long> getTabsCount();
Map<Integer, Long> getTabsCount(ProductSpuPageReqVO reqVO);
/**
* categoryId SPU

View File

@ -256,23 +256,19 @@ public class ProductSpuServiceImpl implements ProductSpuService {
}
@Override
public Map<Integer, Long> getTabsCount() {
public Map<Integer, Long> getTabsCount(ProductSpuPageReqVO reqVO) {
Map<Integer, Long> counts = Maps.newLinkedHashMapWithExpectedSize(5);
// 查询销售中的商品数量
// 每个 tab 的数量 = 筛选条件name/categoryId/createTime+ 该 tab 的状态/库存条件
counts.put(ProductSpuPageReqVO.FOR_SALE,
productSpuMapper.selectCount(ProductSpuDO::getStatus, ProductSpuStatusEnum.ENABLE.getStatus()));
// 查询仓库中的商品数量
productSpuMapper.selectCountByTab(reqVO, ProductSpuPageReqVO.FOR_SALE));
counts.put(ProductSpuPageReqVO.IN_WAREHOUSE,
productSpuMapper.selectCount(ProductSpuDO::getStatus, ProductSpuStatusEnum.DISABLE.getStatus()));
// 查询售空的商品数量
productSpuMapper.selectCountByTab(reqVO, ProductSpuPageReqVO.IN_WAREHOUSE));
counts.put(ProductSpuPageReqVO.SOLD_OUT,
productSpuMapper.selectCount(ProductSpuDO::getStock, 0));
// 查询触发警戒库存的商品数量
productSpuMapper.selectCountByTab(reqVO, ProductSpuPageReqVO.SOLD_OUT));
counts.put(ProductSpuPageReqVO.ALERT_STOCK,
productSpuMapper.selectCount());
// 查询回收站中的商品数量
productSpuMapper.selectCountByTab(reqVO, ProductSpuPageReqVO.ALERT_STOCK));
counts.put(ProductSpuPageReqVO.RECYCLE_BIN,
productSpuMapper.selectCount(ProductSpuDO::getStatus, ProductSpuStatusEnum.RECYCLE.getStatus()));
productSpuMapper.selectCountByTab(reqVO, ProductSpuPageReqVO.RECYCLE_BIN));
return counts;
}

View File

@ -4,10 +4,12 @@ import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageItemRespVO;
import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponRespVO;
import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponSendReqVO;
import cn.iocoder.yudao.module.promotion.convert.coupon.CouponConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
@ -15,12 +17,12 @@ import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@ -63,6 +65,15 @@ public class CouponController {
return success(pageResulVO);
}
@GetMapping("/get")
@Operation(summary = "获得优惠劵")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('promotion:coupon:query')")
public CommonResult<CouponRespVO> getCoupon(@RequestParam("id") Long id) {
CouponDO coupon = couponService.getCoupon(id);
return success(BeanUtils.toBean(coupon, CouponRespVO.class));
}
@PostMapping("/send")
@Operation(summary = "发送优惠劵")
@PreAuthorize("@ss.hasPermission('promotion:coupon:send')")

View File

@ -160,7 +160,7 @@ public interface CouponService {
Map<Long, Boolean> getUserCanCanTakeMap(Long userId, List<CouponTemplateDO> templates);
/**
*
*
*
* @param userId
* @param id
@ -168,4 +168,12 @@ public interface CouponService {
*/
CouponDO getCoupon(Long userId, Long id);
/**
*
*
* @param id
* @return
*/
CouponDO getCoupon(Long id);
}

View File

@ -378,6 +378,11 @@ public class CouponServiceImpl implements CouponService {
return couponMapper.selectByIdAndUserId(id, userId);
}
@Override
public CouponDO getCoupon(Long id) {
return couponMapper.selectById(id);
}
private CouponDO validateCouponExists(Long id) {
CouponDO coupon = couponMapper.selectById(id);
if (coupon == null) {

View File

@ -73,7 +73,7 @@ public class DeliveryPickUpStoreController {
}
List<AdminUserRespDTO> verifyUsers = CollUtil.isNotEmpty(deliveryPickUpStore.getVerifyUserIds()) ?
adminUserApi.getUserList(deliveryPickUpStore.getVerifyUserIds()).getCheckedData() : null;
return success(BeanUtils.toBean(deliveryPickUpStore, DeliveryPickUpStoreRespVO.class)
return success(DeliveryPickUpStoreConvert.INSTANCE.convert01(deliveryPickUpStore)
.setVerifyUsers(BeanUtils.toBean(verifyUsers, UserSimpleBaseVO.class)));
}

View File

@ -21,6 +21,9 @@ public class DeliveryPickUpStoreRespVO extends DeliveryPickUpStoreBaseVO {
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "地区名称", example = "上海上海市普陀区")
private String areaName;
@Schema(description = "核销用户数组", requiredMode = Schema.RequiredMode.REQUIRED)
private List<UserSimpleBaseVO> verifyUsers;

View File

@ -30,6 +30,9 @@ public interface DeliveryPickUpStoreConvert {
PageResult<DeliveryPickUpStoreRespVO> convertPage(PageResult<DeliveryPickUpStoreDO> page);
@Mapping(source = "areaId", target = "areaName", qualifiedByName = "convertAreaIdToAreaName")
DeliveryPickUpStoreRespVO convert01(DeliveryPickUpStoreDO bean);
List<DeliveryPickUpStoreSimpleRespVO> convertList1(List<DeliveryPickUpStoreDO> list);
@Mapping(source = "areaId", target = "areaName", qualifiedByName = "convertAreaIdToAreaName")
DeliveryPickUpStoreSimpleRespVO convert02(DeliveryPickUpStoreDO bean);

View File

@ -16,4 +16,7 @@ public class AddressRespVO extends AddressBaseVO {
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "地区名称", example = "上海上海市普陀区")
private String areaName;
}

View File

@ -40,6 +40,9 @@ public interface AddressConvert {
return AreaUtils.format(areaId);
}
@Mapping(source = "areaId", target = "areaName", qualifiedByName = "convertAreaIdToAreaName")
AddressRespVO convert03(MemberAddressDO bean);
List<AddressRespVO> convertList2(List<MemberAddressDO> list);
}

View File

@ -43,7 +43,7 @@ public class SubscribeHandler implements WxMpMessageHandler {
} catch (WxErrorException e) {
log.error("[handle][粉丝({})] 获取粉丝信息失败!", wxMessage.getFromUser(), e);
// 特殊情况个人账号无接口权限https://t.zsxq.com/cLFq5
if (ObjUtil.equal(e.getError().getErrorCode(), WxMpErrorMsgEnum.CODE_48001)) {
if (ObjUtil.equal(e.getError().getErrorCode(), WxMpErrorMsgEnum.CODE_48001.getCode())) {
wxMpUser = new WxMpUser();
wxMpUser.setOpenId(wxMessage.getFromUser());
wxMpUser.setSubscribe(true);

View File

@ -71,6 +71,15 @@ public class PayChannelController {
return success(PayChannelConvert.INSTANCE.convert(channel));
}
@GetMapping("/list")
@Operation(summary = "获得指定应用的支付渠道列表")
@Parameter(name = "appId", description = "应用编号", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('pay:channel:query')")
public CommonResult<List<PayChannelRespVO>> getChannelList(@RequestParam("appId") Long appId) {
List<PayChannelDO> list = channelService.getChannelListByAppId(appId);
return success(PayChannelConvert.INSTANCE.convertList(list));
}
@GetMapping("/get-enable-code-list")
@Operation(summary = "获得指定应用的开启的支付渠道编码列表")
@Parameter(name = "appId", description = "应用编号", required = true, example = "1")

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.pay.convert.channel;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelCreateReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelRespVO;
import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelUpdateReqVO;
@ -9,6 +10,8 @@ import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper
public interface PayChannelConvert {
@ -23,6 +26,15 @@ public interface PayChannelConvert {
@Mapping(target = "config",expression = "java(cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString(bean.getConfig()))")
PayChannelRespVO convert(PayChannelDO bean);
default List<PayChannelRespVO> convertList(List<PayChannelDO> list) {
// 列表不下发 config密钥/证书),详情接口再返回完整配置
return CollectionUtils.convertList(list, channel -> {
PayChannelRespVO vo = convert(channel);
vo.setConfig(null);
return vo;
});
}
PageResult<PayChannelRespVO> convertPage(PageResult<PayChannelDO> page);
}

View File

@ -28,4 +28,8 @@ public interface PayChannelMapper extends BaseMapperX<PayChannelDO> {
.eq(PayChannelDO::getStatus, status));
}
default List<PayChannelDO> selectListByAppId(Long appId) {
return selectList(PayChannelDO::getAppId, appId);
}
}

View File

@ -93,6 +93,14 @@ public interface PayChannelService {
*/
List<PayChannelDO> getEnableChannelList(Long appId);
/**
*
*
* @param appId
* @return
*/
List<PayChannelDO> getChannelListByAppId(Long appId);
/**
*
*

View File

@ -156,6 +156,11 @@ public class PayChannelServiceImpl implements PayChannelService {
return payChannelMapper.selectListByAppId(appId, CommonStatusEnum.ENABLE.getStatus());
}
@Override
public List<PayChannelDO> getChannelListByAppId(Long appId) {
return payChannelMapper.selectListByAppId(appId);
}
@Override
public PayClient getPayClient(Long id) {
PayChannelDO channel = validPayChannel(id);

View File

@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.SecureUtil;
@ -10,6 +9,7 @@ import cn.hutool.crypto.digest.HmacAlgorithm;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
@ -120,7 +120,7 @@ public class QiniuSmsClient extends AbstractSmsClient {
.setSuccess("DELIVRD".equals(statusObj.getStr("status"))) // 是否接收成功
.setErrorMsg(statusObj.getStr("status")) // 状态报告编码
.setMobile(statusObj.getStr("mobile")) // 手机号
.setReceiveTime(LocalDateTimeUtil.of(statusObj.getLong("delivrd_at") * 1000L)) // 状态报告时间
.setReceiveTime(LocalDateTimeUtils.ofEpochSecond(statusObj.getLong("delivrd_at"))) // 状态报告时间
.setSerialNo(statusObj.getStr("message_id")) // 发送序列号
.setLogId(statusObj.getLong("seq")); // 用户序列号
}

View File

@ -14,7 +14,6 @@ import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemSkuDO;
import org.apache.ibatis.annotations.Mapper;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@ -100,11 +99,8 @@ public interface WmsInventoryMapper extends BaseMapperX<WmsInventoryDO> {
if (CollUtil.isEmpty(ids)) {
return Collections.emptyList();
}
List<Long> sortedIds = new ArrayList<>(ids);
Collections.sort(sortedIds);
return selectList(new LambdaQueryWrapperX<WmsInventoryDO>()
.in(WmsInventoryDO::getId, sortedIds)
.orderByAsc(WmsInventoryDO::getId)
.in(WmsInventoryDO::getId, ids)
.last("FOR UPDATE"));
}