feature(REQ-3282): 调整api引入的包。以及node的部分实现

This commit is contained in:
周敏 2024-12-10 15:31:34 +08:00
parent c1108dd54a
commit 34e89461bb
18 changed files with 268 additions and 181 deletions

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.axzo.orgmanax</groupId>
@ -23,9 +24,9 @@
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>cn.axzo.framework</groupId>
<artifactId>axzo-common-domain</artifactId>
<version>1.0.0-SNAPSHOT</version>
<groupId>cn.axzo.foundation</groupId>
<artifactId>common-lib</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>

View File

@ -1,8 +1,13 @@
package cn.axzo.orgmanax.api.node.feign;
import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.foundation.page.PageResp;
import cn.axzo.foundation.result.ApiResult;
import cn.axzo.orgmanax.api.node.req.NodeListReq;
import cn.axzo.orgmanax.api.node.req.NodeProcessReq;
import cn.axzo.orgmanax.api.node.resp.OrgNodeDTO;
import com.alibaba.fastjson.JSONObject;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@ -20,10 +25,19 @@ public interface OrgNodeApi {
/**
* 处理部门相关的逻辑
*
* @param param why json: 由于不同行为需要的参数和返回信息均不一致jsonObject用来统一接收参数和返回数据具体消费参数的地方serviceprocessor来定义具体的类型
* @param req
* @return
*/
@PostMapping("/api/org/node/process")
ApiResult<JSONObject> process(@RequestBody JSONObject param);
ApiResult<JSONObject> process(@RequestBody @Validated NodeProcessReq req);
/**
* 分页列表接口
*
* @param req
* @return
*/
@PostMapping("/api/org/node/list")
ApiResult<PageResp<OrgNodeDTO>> list(@RequestBody @Validated NodeListReq req);
}

View File

@ -0,0 +1,36 @@
package cn.axzo.orgmanax.api.node.req;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import java.util.List;
import java.util.Set;
@NoArgsConstructor
@AllArgsConstructor
@Data
@SuperBuilder
public class NodeListReq {
private Long id;
private Set<Long> ids;
/**
* 用于遍历数据
*/
private Long idGt;
private Long organizationalUnitId;
private Long organizationalUnitIds;
private Boolean needParent;
// 分页参数
@Builder.Default
private Integer page = 1;
/**
* 最大支持1000条数据不支持单页超过1000的查询接入方按需分页
*/
@Builder.Default
private Integer pageSize = 1000;
private List<String> sort;
}

View File

@ -8,50 +8,14 @@ import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* @author tanjie@axzo.cn
* @date 2024/12/2 16:09
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ProcessNodeReq {
/**
* 单位ID
*/
private Long organizationalUnitId;
/**
* 部门名称
*/
private String organizationalNodeName;
/**
* 部门类型
*/
private Integer nodeType;
/**
* 上级部门ID
*/
private Long parentId;
/**
* 简介
*/
private String remark;
/**
* 操作人
*/
public class NodeProcessReq {
private Long id;
private Long operatorId;
/**
* 参数
*/
private Action action;
private JSONObject param;
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@ -65,5 +29,5 @@ public class ProcessNodeReq {
private final String desc;
private final String processor;
}
}

View File

@ -0,0 +1,100 @@
package cn.axzo.orgmanax.api.node.resp;
import com.alibaba.fastjson.JSONObject;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class OrgNodeDTO implements Serializable {
protected Long id;
/**
* 上级id,为0时为顶级节点
*/
private Long parentId;
/**
* 单位id
*/
private Long organizationalUnitId;
/**
* 节点类型 1.部门 2.班组 3.小组
*/
private Integer nodeType;
/**
* 节点名称
*/
private String nodeName;
/**
* 层级路由
*/
private String path;
/**
* 扩展字段
*/
private JSONObject extra;
/**
* 排序
*/
private Integer sort;
/**
* 节点描述
*/
private String remark;
/**
* 迁移数据原始id
*/
private Long tempTransferId;
/**
* 顶级节点的nodeId
*/
private Long topNodeId;
/**
* 节点状态 1-正常 0-停用
**/
private Integer nodeState;
/**
* 节点同步状态 1-正常 0-停用
**/
private Integer syncState;
/**
* 节点扩展类型 1-正常 2-隐藏
**/
private Integer extType;
/**
* 节点同步ID
**/
private Long syncDataId;
private String createScene;
/**
* 节点信息json不同的节点类型格式不一如班组节点的解散状态等
*/
private JSONObject profile;
protected Date createAt;
protected Long createBy;
protected Date updateAt;
protected Long updateBy;
protected Long isDelete = 0L;
}

View File

@ -1,76 +0,0 @@
package cn.axzo.orgmanax.api.node.resp;
import com.alibaba.fastjson.JSONObject;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* 部门信息
*
* @author tanjie@axzo.cn
* @date 2024/12/2 16:20
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ProcessOrgNodeResp {
/**
* 部门ID
*/
private Long id;
/**
* 单位id
*/
private Long organizationalUnitId;
/**
* 部门名称
*/
private String organizationalNodeName;
/**
* 节点类型 节点类型 1.部门 2.班组 3.小组
*/
private Integer nodeType;
/**
* 上级部门ID
*/
private Long parentId;
/**
* 顶级部门ID
*/
private Long topNodeId;
/**
* 层级信息
*/
private String path;
/**
* 状态
*/
private Integer status;
/**
* 创建人自然人ID
*/
private Long createBy;
/**
* 创建时间
*/
private Date createAt;
/**
* 业务数据业务需要使用的数据小组-盈亏模型平台班组-platTeamId等不同nodeType里面的内容不一样
*/
private JSONObject bizData;
/**
* 扩展信息
*/
private JSONObject extra;
}

View File

@ -1,6 +1,7 @@
package cn.axzo.orgmanax.api.unit.feign;
import cn.axzo.foundation.result.ApiResult;
import cn.axzo.orgmanax.api.unit.req.CreateOrgUnitReq;
import cn.axzo.orgmanax.api.unit.req.ListOrgUnitReq;
import cn.axzo.orgmanax.api.unit.req.UpdateOrgUnitReq;
@ -12,6 +13,7 @@ import org.springframework.web.bind.annotation.PostMapping;
/**
* 单位API
*
* @author tanjie@axzo.cn
*/
@FeignClient(
@ -20,12 +22,12 @@ import org.springframework.web.bind.annotation.PostMapping;
public interface OrgUnitApi {
@PostMapping("/api/org/unit/create")
CreateOrgUnitResp create(CreateOrgUnitReq req);
ApiResult<CreateOrgUnitResp> create(CreateOrgUnitReq req);
@PostMapping("/api/org/unit/update")
UpdateOrgUnitResp update(UpdateOrgUnitReq req);
ApiResult<UpdateOrgUnitResp> update(UpdateOrgUnitReq req);
@PostMapping("/api/org/unit/list")
ListOrgUnitResp list(ListOrgUnitReq req);
ApiResult<ListOrgUnitResp> list(ListOrgUnitReq req);
}

View File

@ -119,6 +119,8 @@ public class OrganizationalNode implements Serializable {
@TableField("sync_data_id")
private Long syncDataId;
private String createScene;
/**
* 节点信息json不同的节点类型格式不一如班组节点的解散状态等
*/

View File

@ -1,6 +1,7 @@
package cn.axzo.orgmanax.infra.dao.node.repository;
import cn.axzo.foundation.dao.support.mysql.QueryWrapperHelper;
import cn.axzo.foundation.dao.support.wrapper.CriteriaField;
import cn.axzo.foundation.dao.support.wrapper.Operator;
import cn.axzo.foundation.page.IPageReq;
@ -8,6 +9,7 @@ import cn.axzo.foundation.page.PageResp;
import cn.axzo.orgmanax.infra.dao.node.DO.OrganizationalNode;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.google.common.base.Preconditions;
import lombok.AllArgsConstructor;
import lombok.Data;
@ -25,7 +27,12 @@ import java.util.Set;
*/
public interface NodeQueryRepository {
List<NodeResp> list(ListReq req);
default List<NodeResp> list(ListReq req) {
PageReq pageReq = BeanUtil.toBean(req, PageReq.class);
pageReq.setPage(1);
pageReq.setPageSize(1000);
return page(pageReq).getData();
}
PageResp<NodeResp> page(PageReq req);
@ -75,6 +82,12 @@ public interface NodeQueryRepository {
@CriteriaField
private Boolean needParent;
public QueryWrapper<OrganizationalNode> toWrapper() {
QueryWrapper<OrganizationalNode> wrapper = QueryWrapperHelper.fromBean(this, OrganizationalNode.class);
return wrapper;
}
}
@EqualsAndHashCode(callSuper = true)

View File

@ -1,18 +1,24 @@
package cn.axzo.orgmanax.infra.dao.node.repository.impl;
import cn.axzo.foundation.dao.support.mysql.QueryWrapperHelper;
import cn.axzo.foundation.dao.support.converter.PageConverter;
import cn.axzo.foundation.page.PageResp;
import cn.axzo.orgmanax.infra.dao.node.DO.OrganizationalNode;
import cn.axzo.orgmanax.infra.dao.node.dao.NodeDao;
import cn.axzo.orgmanax.infra.dao.node.repository.NodeQueryRepository;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.BooleanUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.google.common.collect.Sets;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author tanjie@axzo.cn
@ -25,18 +31,34 @@ public class NodeQueryRepositoryImpl implements NodeQueryRepository {
private final NodeDao nodeDao;
@Override
public List<NodeResp> list(ListReq req) {
QueryWrapper<OrganizationalNode> wrapper = QueryWrapperHelper.fromBean(this, OrganizationalNode.class);
List<NodeResp> results = nodeDao.list(wrapper).stream().map(e -> BeanUtil.toBean(e, NodeResp.class)).collect(Collectors.toList());
if (BooleanUtil.isTrue(req.getNeedParent())) {
// assemble parentNode
public PageResp<NodeResp> page(PageReq req) {
IPage<OrganizationalNode> page = PageConverter.toMybatis(req, OrganizationalNode.class);
IPage<NodeResp> results = nodeDao.page(page, req.toWrapper())
.convert(e -> BeanUtil.toBean(e, NodeResp.class));
PageResp<NodeResp> resp = PageConverter.toResp(results);
List<NodeResp> records = resp.getData();
if (CollUtil.isEmpty(records)) {
return resp;
}
return results;
// assemble parent node
assembleParentNode(req, records);
return resp;
}
@Override
public PageResp<NodeResp> page(PageReq req) {
// TODO
return null;
private void assembleParentNode(PageReq req, List<NodeResp> records) {
if (!BooleanUtil.isTrue(req.getNeedParent())) {
return;
}
Map<Long, NodeResp> nodesById = records.stream().collect(Collectors.toMap(NodeResp::getId, Function.identity()));
// assemble parentNode
Set<Long> parentNodeIds = records.stream().map(OrganizationalNode::getParentId).collect(Collectors.toSet());
Set<Long> needQueryDbParentNodeIds = Sets.difference(nodesById.keySet(), parentNodeIds);
Stream<NodeResp> queryDbParentNodes = needQueryDbParentNodeIds.isEmpty() ? Stream.of()
: nodeDao.listByIds(needQueryDbParentNodeIds).stream().map(e -> BeanUtil.toBean(e, NodeResp.class));
Map<Long, NodeResp> parentNodeByParentId = Stream.concat(records.stream().filter(s -> parentNodeIds.contains(s.getId())), queryDbParentNodes)
.collect(Collectors.toMap(OrganizationalNode::getId, Function.identity()));
records.forEach(r -> r.setParentNode(parentNodeByParentId.get(r.getParentId())));
}
}

View File

@ -1,5 +1,6 @@
package cn.axzo.orgmanax.infra.dao.node.repository.impl;
import cn.axzo.foundation.exception.Axssert;
import cn.axzo.orgmanax.infra.dao.node.DO.OrganizationalNode;
import cn.axzo.orgmanax.infra.dao.node.dao.NodeDao;
import cn.axzo.orgmanax.infra.dao.node.repository.NodeUpsertRepository;
@ -20,13 +21,13 @@ public class NodeUpsertRepositoryImpl implements NodeUpsertRepository {
@Override
public OrganizationalNode create(OrganizationalNode node) {
nodeDao.save(node);
node.setId(node.getId());
return node;
return nodeDao.getById(node.getId());
}
@Override
public OrganizationalNode update(OrganizationalNode node) {
Axssert.checkNonNull(node.getId(), "更新部门部门id不能为空");
nodeDao.updateById(node);
return node;
return nodeDao.getById(node.getId());
}
}

View File

@ -1,9 +1,12 @@
package cn.axzo.orgmanax.server.node.controller;
import cn.axzo.foundation.exception.Axssert;
import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.foundation.page.PageResp;
import cn.axzo.foundation.result.ApiResult;
import cn.axzo.orgmanax.api.node.feign.OrgNodeApi;
import cn.axzo.orgmanax.api.node.req.ProcessNodeReq;
import cn.axzo.orgmanax.api.node.req.NodeListReq;
import cn.axzo.orgmanax.api.node.req.NodeProcessReq;
import cn.axzo.orgmanax.api.node.resp.OrgNodeDTO;
import cn.axzo.orgmanax.server.node.service.NodeService;
import cn.axzo.orgmanax.server.node.service.processor.NodeProcessor;
import com.alibaba.fastjson.JSONObject;
@ -29,22 +32,18 @@ public class NodeController implements OrgNodeApi {
* <p>
* XXX: why json: 由于不同行为需要的参数和返回信息均不一致jsonObject用来统一接收参数和返回数据具体消费参数的地方serviceprocessor来定义具体的类型
*
* @param param 创建部门参数
* @param req 创建部门参数
* @return
*/
@Override
public ApiResult<JSONObject> process(JSONObject param) {
NodeService.ProcessReq processReq = NodeService.ProcessReq.builder()
.id(param.getLong("id"))
.operatorId(param.getLong("operatorId"))
.action(param.getObject("action", ProcessNodeReq.Action.class))
.param(param)
.build();
NodeProcessor.ProcessResult processResult = nodeService.process(processReq);
public ApiResult<JSONObject> process(NodeProcessReq req) {
NodeProcessor.ProcessResult processResult = nodeService.process(req);
Axssert.isTrue(processResult.getSuccess(), NODE_PROCESS_FAILED, processResult.getMsg());
return ApiResult.ok(processResult.getContext());
return ApiResult.success(processResult.getContext());
}
@Override
public ApiResult<PageResp<OrgNodeDTO>> list(NodeListReq req) {
return ApiResult.success(nodeService.page(req));
}
}

View File

@ -1,6 +1,5 @@
package cn.axzo.orgmanax.server.node.foundation;
import cn.axzo.orgmanax.api.node.resp.ProcessOrgNodeResp;
import cn.axzo.orgmanax.infra.dao.node.DO.OrganizationalNode;
import cn.axzo.orgmanax.server.node.foundation.entity.NodeCreate;
import org.springframework.transaction.annotation.Transactional;

View File

@ -1,5 +1,6 @@
package cn.axzo.orgmanax.server.node.foundation.impl;
import cn.axzo.foundation.event.support.producer.EventProducer;
import cn.axzo.orgmanax.common.config.BizResultCode;
import cn.axzo.orgmanax.infra.dao.node.DO.OrganizationalNode;
import cn.axzo.orgmanax.infra.dao.node.repository.NodeQueryRepository;
@ -21,7 +22,7 @@ public class NodeFoundationServiceImpl implements NodeFoundationService {
private final NodeQueryRepository nodeQueryRepository;
private final NodeUpsertRepository nodeUpsertRepository;
private final EventProducer eventProducer;
/**
* 创建部门标准接口

View File

@ -2,9 +2,10 @@ package cn.axzo.orgmanax.server.node.service;
import cn.axzo.foundation.exception.Axssert;
import cn.axzo.foundation.page.PageResp;
import cn.axzo.orgmanax.api.node.req.ProcessNodeReq;
import cn.axzo.orgmanax.api.node.req.NodeListReq;
import cn.axzo.orgmanax.api.node.req.NodeProcessReq;
import cn.axzo.orgmanax.api.node.resp.OrgNodeDTO;
import cn.axzo.orgmanax.common.config.BizResultCode;
import cn.axzo.orgmanax.infra.dao.node.repository.NodeQueryRepository;
import cn.axzo.orgmanax.server.node.service.processor.NodeProcessor;
import com.alibaba.fastjson.JSONObject;
import lombok.AllArgsConstructor;
@ -16,11 +17,13 @@ import java.util.List;
public interface NodeService {
NodeProcessor.ProcessResult process(ProcessReq req);
NodeProcessor.ProcessResult process(NodeProcessReq req);
List<NodeQueryRepository.NodeResp> list(NodeQueryRepository.ListReq req);
PageResp<OrgNodeDTO> page(NodeListReq req);
PageResp<NodeQueryRepository.NodeResp> page(NodeQueryRepository.PageReq req);
default List<OrgNodeDTO> list(NodeListReq req) {
return page(req).getData();
}
@NoArgsConstructor
@AllArgsConstructor
@ -29,7 +32,7 @@ public interface NodeService {
class ProcessReq {
private Long id;
private Long operatorId;
private ProcessNodeReq.Action action;
private NodeProcessReq.Action action;
private JSONObject param;
/**
@ -41,5 +44,4 @@ public interface NodeService {
}
}
}

View File

@ -1,16 +1,22 @@
package cn.axzo.orgmanax.server.node.service.impl;
import cn.axzo.foundation.exception.Axssert;
import cn.axzo.foundation.page.PageResp;
import cn.axzo.orgmanax.api.node.req.NodeListReq;
import cn.axzo.orgmanax.api.node.req.NodeProcessReq;
import cn.axzo.orgmanax.api.node.resp.OrgNodeDTO;
import cn.axzo.orgmanax.common.config.BizResultCode;
import cn.axzo.orgmanax.infra.dao.node.repository.NodeQueryRepository;
import cn.axzo.orgmanax.server.node.service.NodeService;
import cn.axzo.orgmanax.server.node.service.processor.NodeProcessor;
import cn.hutool.core.bean.BeanUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Service
@ -21,9 +27,10 @@ public class NodeServiceImpl implements NodeService {
private final ApplicationContext applicationContext;
@Override
public NodeProcessor.ProcessResult process(ProcessReq req) {
public NodeProcessor.ProcessResult process(NodeProcessReq req) {
// 通用check
req.check();
Axssert.notNull(req.getAction(), BizResultCode.INVALID_PARAM, "执行行为不能为空");
Axssert.notNull(req.getOperatorId(), BizResultCode.INVALID_PARAM, "操作人不能为空");
// 如果指定了id校验部门是否存在并加载出node以便后续使用
NodeQueryRepository.NodeResp current = req.getId() == null ? null
: nodeQueryRepository.oneOpt(NodeQueryRepository.OneReq.builder().id(req.getId()).build())
@ -42,12 +49,11 @@ public class NodeServiceImpl implements NodeService {
}
@Override
public List<NodeQueryRepository.NodeResp> list(NodeQueryRepository.ListReq req) {
return nodeQueryRepository.list(req);
}
@Override
public PageResp<NodeQueryRepository.NodeResp> page(NodeQueryRepository.PageReq req) {
return nodeQueryRepository.page(req);
public PageResp<OrgNodeDTO> page(NodeListReq req) {
NodeQueryRepository.PageReq pageReq = BeanUtil.toBean(req, NodeQueryRepository.PageReq.class);
PageResp<NodeQueryRepository.NodeResp> page = nodeQueryRepository.page(pageReq);
List<OrgNodeDTO> records = page.getData().stream().map(e -> BeanUtil.toBean(e, OrgNodeDTO.class)).collect(Collectors.toList());
// assemble data if needed
return new PageResp<>(page.getTotal(), page.getSize(), page.getCurrent(), records);
}
}

View File

@ -1,6 +1,6 @@
package cn.axzo.orgmanax.server.node.service.processor;
import cn.axzo.orgmanax.api.node.req.ProcessNodeReq;
import cn.axzo.orgmanax.api.node.req.NodeProcessReq;
import cn.axzo.orgmanax.infra.dao.node.repository.NodeQueryRepository;
import com.alibaba.fastjson.JSONObject;
import lombok.AccessLevel;
@ -24,7 +24,7 @@ public interface NodeProcessor {
@AllArgsConstructor
class ProcessContext {
NodeQueryRepository.NodeResp node;
ProcessNodeReq.Action action;
NodeProcessReq.Action action;
JSONObject params;

View File

@ -1,11 +1,11 @@
package cn.axzo.orgmanax.server.node.service.processor.impl;
import cn.axzo.foundation.event.support.producer.EventProducer;
import cn.axzo.foundation.exception.Axssert;
import cn.axzo.orgmanax.api.node.resp.ProcessOrgNodeResp;
import cn.axzo.orgmanax.common.config.BizResultCode;
import cn.axzo.orgmanax.server.node.foundation.NodeFoundationService;
import cn.axzo.orgmanax.server.node.foundation.entity.NodeCreate;
import cn.axzo.orgmanax.server.node.service.processor.NodeProcessor;
import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.AllArgsConstructor;
import lombok.Builder;
@ -31,7 +31,8 @@ public class CreateNodeProcessor implements NodeProcessor {
// basic check
param.check();
// 创建普通部门数据
ProcessOrgNodeResp processOrgNodeResp = null; // nodeFoundationService.create(null);
NodeCreate nodeCreate = BeanUtil.toBean(param, NodeCreate.class);
// ProcessOrgNodeResp processOrgNodeResp = nodeFoundationService.create(nodeCreate);
// 发送部门创建MQ消息 - 业务事件
// eventProducer.send(null);