main
parent
aa427a2e06
commit
9f4d30f933
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<annotationProcessing>
|
||||
<profile name="Maven default annotation processors profile" enabled="true">
|
||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<module name="unis-crm-backend" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
</component>
|
||||
<component name="JavacSettings">
|
||||
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
|
||||
<module name="unis-crm-backend" options="-parameters" />
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/backend/src/main/java" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Central Repository" />
|
||||
<option name="url" value="https://repo.maven.apache.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/backend/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="openjdk-21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/unis_crm.iml" filepath="$PROJECT_DIR$/.idea/unis_crm.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AutoImportSettings">
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="4c558d98-824e-4a48-ba48-bd2e6172f9f4" name="更改" comment="">
|
||||
<change beforePath="$PROJECT_DIR$/backend/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/backend/README.md" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/frontend/dist/assets/index-DTZ3L0iU.js" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/frontend/dist/assets/index-cLTs2L9U.css" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/frontend/dist/index.html" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/dist/index.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/frontend/index.html" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/index.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/frontend/src/App.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/src/App.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/frontend/src/index.css" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/src/index.css" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/frontend/src/pages/Dashboard.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/src/pages/Dashboard.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/frontend/src/pages/Expansion.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/src/pages/Expansion.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/frontend/src/pages/Profile.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/src/pages/Profile.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/frontend/vite.config.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/vite.config.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/package.json" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="MarkdownSettingsMigration">
|
||||
<option name="stateVersion" value="1" />
|
||||
</component>
|
||||
<component name="ProjectColorInfo"><![CDATA[{
|
||||
"associatedIndex": 1
|
||||
}]]></component>
|
||||
<component name="ProjectId" id="3BBm14kQhaD2gQxOS8rBU8WsdoX" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"keyToString": {
|
||||
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"WebServerToolWindowFactoryState": "false",
|
||||
"git-widget-placeholder": "main",
|
||||
"last_opened_file_path": "/Users/kangwenjing/Downloads/crm/unis_crm",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"project.structure.last.edited": "Libraries",
|
||||
"project.structure.proportion": "0.0",
|
||||
"project.structure.side.proportion": "0.0",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
}
|
||||
}]]></component>
|
||||
<component name="RunManager">
|
||||
<configuration name="UnisCrmBackendApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
|
||||
<module name="unis-crm-backend" />
|
||||
<option name="SPRING_BOOT_MAIN_CLASS" value="com.unis.crm.UnisCrmBackendApplication" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="应用程序级" UseSingleDictionary="true" transferred="true" />
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="默认任务">
|
||||
<changelist id="4c558d98-824e-4a48-ba48-bd2e6172f9f4" name="更改" comment="" />
|
||||
<created>1773970620258</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1773970620258</updated>
|
||||
<workItem from="1773970621225" duration="1084000" />
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"java.configuration.updateBuildConfiguration": "automatic"
|
||||
}
|
||||
|
|
@ -1,17 +1,65 @@
|
|||
# Backend
|
||||
|
||||
该目录用于存放后端代码,当前仅完成结构预留,方便后续继续开发。
|
||||
当前已经补齐首页所需的 Java 后端基础工程,并调整为常规 Spring Boot 单体项目目录结构,技术栈如下:
|
||||
|
||||
建议后续可按下面方式扩展:
|
||||
- Java 17
|
||||
- Spring Boot 3.2.2
|
||||
- MyBatis Plus 3.5.6
|
||||
- PostgreSQL
|
||||
- Redis
|
||||
|
||||
## 已实现内容
|
||||
|
||||
- 首页聚合接口:`GET /api/dashboard/home`
|
||||
- 首页欢迎信息:姓名、职位、部门、入职天数
|
||||
- 首页统计卡片
|
||||
- 首页待办列表
|
||||
- 首页最新动态
|
||||
- 通用响应体与全局异常处理
|
||||
|
||||
## 目录结构
|
||||
|
||||
```text
|
||||
backend/
|
||||
├── src/
|
||||
│ ├── controllers/
|
||||
│ ├── routes/
|
||||
│ ├── services/
|
||||
│ ├── models/
|
||||
│ └── app.ts
|
||||
├── package.json
|
||||
└── README.md
|
||||
├── pom.xml
|
||||
└── src/main
|
||||
├── java/com/unis/crm
|
||||
│ ├── common
|
||||
│ ├── controller
|
||||
│ ├── dto
|
||||
│ ├── mapper
|
||||
│ ├── service
|
||||
│ └── UnisCrmBackendApplication.java
|
||||
└── resources
|
||||
├── application.yml
|
||||
└── mapper/dashboard
|
||||
```
|
||||
|
||||
## 启动前准备
|
||||
|
||||
1. 执行数据库脚本:
|
||||
|
||||
```bash
|
||||
psql -h 127.0.0.1 -U postgres -d nex_auth -f sql/init_pg17.sql
|
||||
```
|
||||
|
||||
2. 确保 `sys_user`、`work_todo`、`sys_activity_log`、`crm_customer`、`crm_opportunity`、`work_checkin` 中有业务数据。
|
||||
|
||||
## 启动项目
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
默认启动在 `8081` 端口,供前端开发环境通过 Vite 代理访问;`8080` 可继续保留给现有认证/系统服务。
|
||||
|
||||
## 首页接口
|
||||
|
||||
请求示例:
|
||||
|
||||
```bash
|
||||
curl -H "X-User-Id: 1" "http://127.0.0.1:8081/api/dashboard/home"
|
||||
```
|
||||
|
||||
首页接口只允许查询当前登录用户自己的数据,必须通过 `X-User-Id` 传入当前用户ID,不支持指定其他用户查询。
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,80 @@
|
|||
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.2.2</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<groupId>com.unis.crm</groupId>
|
||||
<artifactId>unis-crm-backend</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<name>unis-crm-backend</name>
|
||||
<description>UNIS CRM backend</description>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<mybatis-plus.version>3.5.6</mybatis-plus.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.unisbase</groupId>
|
||||
<artifactId>unisbase-spring-boot-starter</artifactId>
|
||||
<version>0.1.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<mainClass>com.unis.crm.UnisCrmBackendApplication</mainClass>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.unis.crm;
|
||||
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication(scanBasePackages = "com.unis.crm")
|
||||
@MapperScan("com.unis.crm.mapper")
|
||||
public class UnisCrmBackendApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(UnisCrmBackendApplication.class, args);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package com.unis.crm.common;
|
||||
|
||||
public class ApiResponse<T> {
|
||||
|
||||
private String code;
|
||||
private String msg;
|
||||
private T data;
|
||||
|
||||
public ApiResponse() {
|
||||
}
|
||||
|
||||
public ApiResponse(String code, String msg, T data) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> success(T data) {
|
||||
return new ApiResponse<>("0", "success", data);
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> fail(String msg) {
|
||||
return new ApiResponse<>("-1", msg, null);
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getMsg() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public void setMsg(String msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public T getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(T data) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.unis.crm.common;
|
||||
|
||||
public class BusinessException extends RuntimeException {
|
||||
|
||||
public BusinessException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package com.unis.crm.common;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
@RestControllerAdvice
|
||||
public class CrmGlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public ApiResponse<Object> handleBusinessException(BusinessException ex) {
|
||||
return ApiResponse.fail(ex.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public ApiResponse<Object> handleValidationException(MethodArgumentNotValidException ex) {
|
||||
String message = ex.getBindingResult().getFieldErrors().stream()
|
||||
.findFirst()
|
||||
.map(error -> error.getField() + " " + error.getDefaultMessage())
|
||||
.orElse("请求参数校验失败");
|
||||
return ApiResponse.fail(message);
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public Map<String, Object> handleUnexpectedException(Exception ex, HttpServletRequest request) {
|
||||
Map<String, Object> body = new LinkedHashMap<>();
|
||||
body.put("timestamp", OffsetDateTime.now().toString());
|
||||
body.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value());
|
||||
body.put("error", HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase());
|
||||
body.put("message", ex.getMessage());
|
||||
body.put("path", request.getRequestURI());
|
||||
return body;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.unis.crm.common;
|
||||
|
||||
public final class CurrentUserUtils {
|
||||
|
||||
private CurrentUserUtils() {
|
||||
}
|
||||
|
||||
public static Long requireCurrentUserId(Long headerUserId) {
|
||||
if (headerUserId == null || headerUserId <= 0) {
|
||||
throw new BusinessException("未识别到当前登录用户");
|
||||
}
|
||||
return headerUserId;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.unis.crm.controller;
|
||||
|
||||
import com.unis.crm.common.ApiResponse;
|
||||
import com.unis.crm.dto.dashboard.DashboardHomeDTO;
|
||||
import com.unis.crm.service.DashboardService;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Validated
|
||||
@RestController
|
||||
@RequestMapping("/api/dashboard")
|
||||
public class DashboardController {
|
||||
|
||||
private final DashboardService dashboardService;
|
||||
|
||||
public DashboardController(DashboardService dashboardService) {
|
||||
this.dashboardService = dashboardService;
|
||||
}
|
||||
|
||||
@GetMapping("/home")
|
||||
public ApiResponse<DashboardHomeDTO> getHome(
|
||||
@RequestHeader("X-User-Id") @Min(1) Long userId) {
|
||||
return ApiResponse.success(dashboardService.getHome(userId));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
package com.unis.crm.controller;
|
||||
|
||||
import com.unis.crm.common.ApiResponse;
|
||||
import com.unis.crm.common.CurrentUserUtils;
|
||||
import com.unis.crm.dto.expansion.CreateChannelExpansionRequest;
|
||||
import com.unis.crm.dto.expansion.CreateExpansionFollowUpRequest;
|
||||
import com.unis.crm.dto.expansion.CreateSalesExpansionRequest;
|
||||
import com.unis.crm.dto.expansion.ExpansionMetaDTO;
|
||||
import com.unis.crm.dto.expansion.ExpansionOverviewDTO;
|
||||
import com.unis.crm.dto.expansion.UpdateChannelExpansionRequest;
|
||||
import com.unis.crm.dto.expansion.UpdateSalesExpansionRequest;
|
||||
import com.unis.crm.service.ExpansionService;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/expansion")
|
||||
public class ExpansionController {
|
||||
|
||||
private final ExpansionService expansionService;
|
||||
|
||||
public ExpansionController(ExpansionService expansionService) {
|
||||
this.expansionService = expansionService;
|
||||
}
|
||||
|
||||
@GetMapping("/meta")
|
||||
public ApiResponse<ExpansionMetaDTO> getMeta(@RequestHeader("X-User-Id") Long userId) {
|
||||
CurrentUserUtils.requireCurrentUserId(userId);
|
||||
return ApiResponse.success(expansionService.getMeta());
|
||||
}
|
||||
|
||||
@GetMapping("/overview")
|
||||
public ApiResponse<ExpansionOverviewDTO> getOverview(
|
||||
@RequestHeader("X-User-Id") Long userId,
|
||||
@RequestParam(value = "keyword", required = false) String keyword) {
|
||||
return ApiResponse.success(expansionService.getOverview(CurrentUserUtils.requireCurrentUserId(userId), keyword));
|
||||
}
|
||||
|
||||
@PostMapping("/sales")
|
||||
public ApiResponse<Long> createSales(
|
||||
@RequestHeader("X-User-Id") Long userId,
|
||||
@Valid @RequestBody CreateSalesExpansionRequest request) {
|
||||
return ApiResponse.success(expansionService.createSalesExpansion(CurrentUserUtils.requireCurrentUserId(userId), request));
|
||||
}
|
||||
|
||||
@PostMapping("/channel")
|
||||
public ApiResponse<Long> createChannel(
|
||||
@RequestHeader("X-User-Id") Long userId,
|
||||
@Valid @RequestBody CreateChannelExpansionRequest request) {
|
||||
return ApiResponse.success(expansionService.createChannelExpansion(CurrentUserUtils.requireCurrentUserId(userId), request));
|
||||
}
|
||||
|
||||
@PutMapping("/sales/{id}")
|
||||
public ApiResponse<Void> updateSales(
|
||||
@RequestHeader("X-User-Id") Long userId,
|
||||
@PathVariable("id") Long id,
|
||||
@Valid @RequestBody UpdateSalesExpansionRequest request) {
|
||||
expansionService.updateSalesExpansion(CurrentUserUtils.requireCurrentUserId(userId), id, request);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@PutMapping("/channel/{id}")
|
||||
public ApiResponse<Void> updateChannel(
|
||||
@RequestHeader("X-User-Id") Long userId,
|
||||
@PathVariable("id") Long id,
|
||||
@Valid @RequestBody UpdateChannelExpansionRequest request) {
|
||||
expansionService.updateChannelExpansion(CurrentUserUtils.requireCurrentUserId(userId), id, request);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@PostMapping("/{bizType}/{bizId}/followups")
|
||||
public ApiResponse<Long> createFollowUp(
|
||||
@RequestHeader("X-User-Id") Long userId,
|
||||
@PathVariable("bizType") String bizType,
|
||||
@PathVariable("bizId") Long bizId,
|
||||
@Valid @RequestBody CreateExpansionFollowUpRequest request) {
|
||||
return ApiResponse.success(expansionService.createFollowUp(CurrentUserUtils.requireCurrentUserId(userId), bizType, bizId, request));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.unis.crm.controller;
|
||||
|
||||
import com.unis.crm.common.ApiResponse;
|
||||
import com.unis.crm.common.CurrentUserUtils;
|
||||
import com.unis.crm.dto.opportunity.CreateOpportunityFollowUpRequest;
|
||||
import com.unis.crm.dto.opportunity.CreateOpportunityRequest;
|
||||
import com.unis.crm.dto.opportunity.OpportunityOverviewDTO;
|
||||
import com.unis.crm.service.OpportunityService;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/opportunities")
|
||||
public class OpportunityController {
|
||||
|
||||
private final OpportunityService opportunityService;
|
||||
|
||||
public OpportunityController(OpportunityService opportunityService) {
|
||||
this.opportunityService = opportunityService;
|
||||
}
|
||||
|
||||
@GetMapping("/overview")
|
||||
public ApiResponse<OpportunityOverviewDTO> getOverview(
|
||||
@RequestHeader("X-User-Id") Long userId,
|
||||
@RequestParam(value = "keyword", required = false) String keyword,
|
||||
@RequestParam(value = "stage", required = false) String stage) {
|
||||
return ApiResponse.success(opportunityService.getOverview(CurrentUserUtils.requireCurrentUserId(userId), keyword, stage));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ApiResponse<Long> createOpportunity(
|
||||
@RequestHeader("X-User-Id") Long userId,
|
||||
@Valid @RequestBody CreateOpportunityRequest request) {
|
||||
return ApiResponse.success(opportunityService.createOpportunity(CurrentUserUtils.requireCurrentUserId(userId), request));
|
||||
}
|
||||
|
||||
@PutMapping("/{opportunityId}")
|
||||
public ApiResponse<Long> updateOpportunity(
|
||||
@RequestHeader("X-User-Id") Long userId,
|
||||
@PathVariable("opportunityId") Long opportunityId,
|
||||
@Valid @RequestBody CreateOpportunityRequest request) {
|
||||
return ApiResponse.success(opportunityService.updateOpportunity(CurrentUserUtils.requireCurrentUserId(userId), opportunityId, request));
|
||||
}
|
||||
|
||||
@PostMapping("/{opportunityId}/followups")
|
||||
public ApiResponse<Long> createFollowUp(
|
||||
@RequestHeader("X-User-Id") Long userId,
|
||||
@PathVariable("opportunityId") Long opportunityId,
|
||||
@Valid @RequestBody CreateOpportunityFollowUpRequest request) {
|
||||
return ApiResponse.success(opportunityService.createFollowUp(CurrentUserUtils.requireCurrentUserId(userId), opportunityId, request));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package com.unis.crm.controller;
|
||||
|
||||
import com.unis.crm.common.ApiResponse;
|
||||
import com.unis.crm.common.CurrentUserUtils;
|
||||
import com.unis.crm.dto.work.CreateWorkCheckInRequest;
|
||||
import com.unis.crm.dto.work.CreateWorkDailyReportRequest;
|
||||
import com.unis.crm.dto.work.WorkOverviewDTO;
|
||||
import com.unis.crm.service.WorkService;
|
||||
import jakarta.validation.constraints.DecimalMax;
|
||||
import jakarta.validation.constraints.DecimalMin;
|
||||
import jakarta.validation.Valid;
|
||||
import java.math.BigDecimal;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/work")
|
||||
public class WorkController {
|
||||
|
||||
private final WorkService workService;
|
||||
|
||||
public WorkController(WorkService workService) {
|
||||
this.workService = workService;
|
||||
}
|
||||
|
||||
@GetMapping("/overview")
|
||||
public ApiResponse<WorkOverviewDTO> getOverview(@RequestHeader("X-User-Id") Long userId) {
|
||||
return ApiResponse.success(workService.getOverview(CurrentUserUtils.requireCurrentUserId(userId)));
|
||||
}
|
||||
|
||||
@GetMapping("/reverse-geocode")
|
||||
public ApiResponse<String> reverseGeocode(
|
||||
@RequestHeader("X-User-Id") Long userId,
|
||||
@RequestParam("lat") @DecimalMin(value = "-90.0") @DecimalMax(value = "90.0") BigDecimal latitude,
|
||||
@RequestParam("lon") @DecimalMin(value = "-180.0") @DecimalMax(value = "180.0") BigDecimal longitude) {
|
||||
CurrentUserUtils.requireCurrentUserId(userId);
|
||||
return ApiResponse.success(workService.resolveLocationName(latitude, longitude));
|
||||
}
|
||||
|
||||
@PostMapping("/checkins")
|
||||
public ApiResponse<Long> saveCheckIn(
|
||||
@RequestHeader("X-User-Id") Long userId,
|
||||
@Valid @RequestBody CreateWorkCheckInRequest request) {
|
||||
return ApiResponse.success(workService.saveCheckIn(CurrentUserUtils.requireCurrentUserId(userId), request));
|
||||
}
|
||||
|
||||
@PostMapping("/daily-reports")
|
||||
public ApiResponse<Long> saveDailyReport(
|
||||
@RequestHeader("X-User-Id") Long userId,
|
||||
@Valid @RequestBody CreateWorkDailyReportRequest request) {
|
||||
return ApiResponse.success(workService.saveDailyReport(CurrentUserUtils.requireCurrentUserId(userId), request));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
package com.unis.crm.dto.dashboard;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
public class DashboardActivityDTO {
|
||||
|
||||
private Long id;
|
||||
private String bizType;
|
||||
private Long bizId;
|
||||
private String actionType;
|
||||
private String title;
|
||||
private String content;
|
||||
private Long operatorUserId;
|
||||
private String operatorName;
|
||||
private OffsetDateTime createdAt;
|
||||
private String timeText;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getBizType() {
|
||||
return bizType;
|
||||
}
|
||||
|
||||
public void setBizType(String bizType) {
|
||||
this.bizType = bizType;
|
||||
}
|
||||
|
||||
public Long getBizId() {
|
||||
return bizId;
|
||||
}
|
||||
|
||||
public void setBizId(Long bizId) {
|
||||
this.bizId = bizId;
|
||||
}
|
||||
|
||||
public String getActionType() {
|
||||
return actionType;
|
||||
}
|
||||
|
||||
public void setActionType(String actionType) {
|
||||
this.actionType = actionType;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public Long getOperatorUserId() {
|
||||
return operatorUserId;
|
||||
}
|
||||
|
||||
public void setOperatorUserId(Long operatorUserId) {
|
||||
this.operatorUserId = operatorUserId;
|
||||
}
|
||||
|
||||
public String getOperatorName() {
|
||||
return operatorName;
|
||||
}
|
||||
|
||||
public void setOperatorName(String operatorName) {
|
||||
this.operatorName = operatorName;
|
||||
}
|
||||
|
||||
public OffsetDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public String getTimeText() {
|
||||
return timeText;
|
||||
}
|
||||
|
||||
public void setTimeText(String timeText) {
|
||||
this.timeText = timeText;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
package com.unis.crm.dto.dashboard;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class DashboardHomeDTO {
|
||||
|
||||
private Long userId;
|
||||
private String realName;
|
||||
private String jobTitle;
|
||||
private String deptName;
|
||||
private Long onboardingDays;
|
||||
private List<DashboardStatDTO> stats;
|
||||
private List<DashboardTodoDTO> todos;
|
||||
private List<DashboardActivityDTO> activities;
|
||||
|
||||
public DashboardHomeDTO() {
|
||||
}
|
||||
|
||||
public DashboardHomeDTO(Long userId, String realName, String jobTitle, String deptName, Long onboardingDays,
|
||||
List<DashboardStatDTO> stats, List<DashboardTodoDTO> todos,
|
||||
List<DashboardActivityDTO> activities) {
|
||||
this.userId = userId;
|
||||
this.realName = realName;
|
||||
this.jobTitle = jobTitle;
|
||||
this.deptName = deptName;
|
||||
this.onboardingDays = onboardingDays;
|
||||
this.stats = stats;
|
||||
this.todos = todos;
|
||||
this.activities = activities;
|
||||
}
|
||||
|
||||
public Long getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(Long userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public String getRealName() {
|
||||
return realName;
|
||||
}
|
||||
|
||||
public void setRealName(String realName) {
|
||||
this.realName = realName;
|
||||
}
|
||||
|
||||
public String getJobTitle() {
|
||||
return jobTitle;
|
||||
}
|
||||
|
||||
public void setJobTitle(String jobTitle) {
|
||||
this.jobTitle = jobTitle;
|
||||
}
|
||||
|
||||
public String getDeptName() {
|
||||
return deptName;
|
||||
}
|
||||
|
||||
public void setDeptName(String deptName) {
|
||||
this.deptName = deptName;
|
||||
}
|
||||
|
||||
public Long getOnboardingDays() {
|
||||
return onboardingDays;
|
||||
}
|
||||
|
||||
public void setOnboardingDays(Long onboardingDays) {
|
||||
this.onboardingDays = onboardingDays;
|
||||
}
|
||||
|
||||
public List<DashboardStatDTO> getStats() {
|
||||
return stats;
|
||||
}
|
||||
|
||||
public void setStats(List<DashboardStatDTO> stats) {
|
||||
this.stats = stats;
|
||||
}
|
||||
|
||||
public List<DashboardTodoDTO> getTodos() {
|
||||
return todos;
|
||||
}
|
||||
|
||||
public void setTodos(List<DashboardTodoDTO> todos) {
|
||||
this.todos = todos;
|
||||
}
|
||||
|
||||
public List<DashboardActivityDTO> getActivities() {
|
||||
return activities;
|
||||
}
|
||||
|
||||
public void setActivities(List<DashboardActivityDTO> activities) {
|
||||
this.activities = activities;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.unis.crm.dto.dashboard;
|
||||
|
||||
public class DashboardStatDTO {
|
||||
|
||||
private String name;
|
||||
private Long value;
|
||||
private String metricKey;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Long getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(Long value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getMetricKey() {
|
||||
return metricKey;
|
||||
}
|
||||
|
||||
public void setMetricKey(String metricKey) {
|
||||
this.metricKey = metricKey;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
package com.unis.crm.dto.dashboard;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
public class DashboardTodoDTO {
|
||||
|
||||
private Long id;
|
||||
private String title;
|
||||
private String bizType;
|
||||
private Long bizId;
|
||||
private String priority;
|
||||
private String status;
|
||||
private OffsetDateTime dueDate;
|
||||
private OffsetDateTime createdAt;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getBizType() {
|
||||
return bizType;
|
||||
}
|
||||
|
||||
public void setBizType(String bizType) {
|
||||
this.bizType = bizType;
|
||||
}
|
||||
|
||||
public Long getBizId() {
|
||||
return bizId;
|
||||
}
|
||||
|
||||
public void setBizId(Long bizId) {
|
||||
this.bizId = bizId;
|
||||
}
|
||||
|
||||
public String getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
public void setPriority(String priority) {
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public OffsetDateTime getDueDate() {
|
||||
return dueDate;
|
||||
}
|
||||
|
||||
public void setDueDate(OffsetDateTime dueDate) {
|
||||
this.dueDate = dueDate;
|
||||
}
|
||||
|
||||
public OffsetDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package com.unis.crm.dto.dashboard;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
public class UserWelcomeDTO {
|
||||
|
||||
private Long userId;
|
||||
private String realName;
|
||||
private String jobTitle;
|
||||
private String deptName;
|
||||
private LocalDate hireDate;
|
||||
|
||||
public Long getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(Long userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public String getRealName() {
|
||||
return realName;
|
||||
}
|
||||
|
||||
public void setRealName(String realName) {
|
||||
this.realName = realName;
|
||||
}
|
||||
|
||||
public String getJobTitle() {
|
||||
return jobTitle;
|
||||
}
|
||||
|
||||
public void setJobTitle(String jobTitle) {
|
||||
this.jobTitle = jobTitle;
|
||||
}
|
||||
|
||||
public String getDeptName() {
|
||||
return deptName;
|
||||
}
|
||||
|
||||
public void setDeptName(String deptName) {
|
||||
this.deptName = deptName;
|
||||
}
|
||||
|
||||
public LocalDate getHireDate() {
|
||||
return hireDate;
|
||||
}
|
||||
|
||||
public void setHireDate(LocalDate hireDate) {
|
||||
this.hireDate = hireDate;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
package com.unis.crm.dto.expansion;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ChannelExpansionItemDTO {
|
||||
|
||||
private Long id;
|
||||
private String type;
|
||||
private String name;
|
||||
private String province;
|
||||
private String industry;
|
||||
private String annualRevenue;
|
||||
private String revenue;
|
||||
private Integer size;
|
||||
private String contact;
|
||||
private String contactTitle;
|
||||
private String phone;
|
||||
private String stageCode;
|
||||
private String stage;
|
||||
private Boolean landed;
|
||||
private String expectedSignDate;
|
||||
private String notes;
|
||||
private List<ExpansionFollowUpDTO> followUps = new ArrayList<>();
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getProvince() {
|
||||
return province;
|
||||
}
|
||||
|
||||
public void setProvince(String province) {
|
||||
this.province = province;
|
||||
}
|
||||
|
||||
public String getIndustry() {
|
||||
return industry;
|
||||
}
|
||||
|
||||
public void setIndustry(String industry) {
|
||||
this.industry = industry;
|
||||
}
|
||||
|
||||
public String getAnnualRevenue() {
|
||||
return annualRevenue;
|
||||
}
|
||||
|
||||
public void setAnnualRevenue(String annualRevenue) {
|
||||
this.annualRevenue = annualRevenue;
|
||||
}
|
||||
|
||||
public String getRevenue() {
|
||||
return revenue;
|
||||
}
|
||||
|
||||
public void setRevenue(String revenue) {
|
||||
this.revenue = revenue;
|
||||
}
|
||||
|
||||
public Integer getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(Integer size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public String getContact() {
|
||||
return contact;
|
||||
}
|
||||
|
||||
public void setContact(String contact) {
|
||||
this.contact = contact;
|
||||
}
|
||||
|
||||
public String getContactTitle() {
|
||||
return contactTitle;
|
||||
}
|
||||
|
||||
public void setContactTitle(String contactTitle) {
|
||||
this.contactTitle = contactTitle;
|
||||
}
|
||||
|
||||
public String getPhone() {
|
||||
return phone;
|
||||
}
|
||||
|
||||
public void setPhone(String phone) {
|
||||
this.phone = phone;
|
||||
}
|
||||
|
||||
public String getStageCode() {
|
||||
return stageCode;
|
||||
}
|
||||
|
||||
public void setStageCode(String stageCode) {
|
||||
this.stageCode = stageCode;
|
||||
}
|
||||
|
||||
public String getStage() {
|
||||
return stage;
|
||||
}
|
||||
|
||||
public void setStage(String stage) {
|
||||
this.stage = stage;
|
||||
}
|
||||
|
||||
public Boolean getLanded() {
|
||||
return landed;
|
||||
}
|
||||
|
||||
public void setLanded(Boolean landed) {
|
||||
this.landed = landed;
|
||||
}
|
||||
|
||||
public String getExpectedSignDate() {
|
||||
return expectedSignDate;
|
||||
}
|
||||
|
||||
public void setExpectedSignDate(String expectedSignDate) {
|
||||
this.expectedSignDate = expectedSignDate;
|
||||
}
|
||||
|
||||
public String getNotes() {
|
||||
return notes;
|
||||
}
|
||||
|
||||
public void setNotes(String notes) {
|
||||
this.notes = notes;
|
||||
}
|
||||
|
||||
public List<ExpansionFollowUpDTO> getFollowUps() {
|
||||
return followUps;
|
||||
}
|
||||
|
||||
public void setFollowUps(List<ExpansionFollowUpDTO> followUps) {
|
||||
this.followUps = followUps;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
package com.unis.crm.dto.expansion;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
|
||||
public class CreateChannelExpansionRequest {
|
||||
|
||||
private Long id;
|
||||
|
||||
@NotBlank(message = "渠道名称不能为空")
|
||||
@Size(max = 200, message = "渠道名称不能超过200字符")
|
||||
private String channelName;
|
||||
|
||||
private String province;
|
||||
private String industry;
|
||||
private BigDecimal annualRevenue;
|
||||
private Integer staffSize;
|
||||
private String contactName;
|
||||
private String contactTitle;
|
||||
private String contactMobile;
|
||||
private String stage;
|
||||
private Boolean landedFlag;
|
||||
private LocalDate expectedSignDate;
|
||||
private String remark;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getChannelName() {
|
||||
return channelName;
|
||||
}
|
||||
|
||||
public void setChannelName(String channelName) {
|
||||
this.channelName = channelName;
|
||||
}
|
||||
|
||||
public String getProvince() {
|
||||
return province;
|
||||
}
|
||||
|
||||
public void setProvince(String province) {
|
||||
this.province = province;
|
||||
}
|
||||
|
||||
public String getIndustry() {
|
||||
return industry;
|
||||
}
|
||||
|
||||
public void setIndustry(String industry) {
|
||||
this.industry = industry;
|
||||
}
|
||||
|
||||
public BigDecimal getAnnualRevenue() {
|
||||
return annualRevenue;
|
||||
}
|
||||
|
||||
public void setAnnualRevenue(BigDecimal annualRevenue) {
|
||||
this.annualRevenue = annualRevenue;
|
||||
}
|
||||
|
||||
public Integer getStaffSize() {
|
||||
return staffSize;
|
||||
}
|
||||
|
||||
public void setStaffSize(Integer staffSize) {
|
||||
this.staffSize = staffSize;
|
||||
}
|
||||
|
||||
public String getContactName() {
|
||||
return contactName;
|
||||
}
|
||||
|
||||
public void setContactName(String contactName) {
|
||||
this.contactName = contactName;
|
||||
}
|
||||
|
||||
public String getContactTitle() {
|
||||
return contactTitle;
|
||||
}
|
||||
|
||||
public void setContactTitle(String contactTitle) {
|
||||
this.contactTitle = contactTitle;
|
||||
}
|
||||
|
||||
public String getContactMobile() {
|
||||
return contactMobile;
|
||||
}
|
||||
|
||||
public void setContactMobile(String contactMobile) {
|
||||
this.contactMobile = contactMobile;
|
||||
}
|
||||
|
||||
public String getStage() {
|
||||
return stage;
|
||||
}
|
||||
|
||||
public void setStage(String stage) {
|
||||
this.stage = stage;
|
||||
}
|
||||
|
||||
public Boolean getLandedFlag() {
|
||||
return landedFlag;
|
||||
}
|
||||
|
||||
public void setLandedFlag(Boolean landedFlag) {
|
||||
this.landedFlag = landedFlag;
|
||||
}
|
||||
|
||||
public LocalDate getExpectedSignDate() {
|
||||
return expectedSignDate;
|
||||
}
|
||||
|
||||
public void setExpectedSignDate(LocalDate expectedSignDate) {
|
||||
this.expectedSignDate = expectedSignDate;
|
||||
}
|
||||
|
||||
public String getRemark() {
|
||||
return remark;
|
||||
}
|
||||
|
||||
public void setRemark(String remark) {
|
||||
this.remark = remark;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package com.unis.crm.dto.expansion;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
public class CreateExpansionFollowUpRequest {
|
||||
|
||||
@NotBlank(message = "跟进类型不能为空")
|
||||
private String followUpType;
|
||||
|
||||
@NotBlank(message = "跟进内容不能为空")
|
||||
private String content;
|
||||
|
||||
private String nextAction;
|
||||
|
||||
@NotNull(message = "跟进时间不能为空")
|
||||
private OffsetDateTime followUpTime;
|
||||
|
||||
public String getFollowUpType() {
|
||||
return followUpType;
|
||||
}
|
||||
|
||||
public void setFollowUpType(String followUpType) {
|
||||
this.followUpType = followUpType;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public String getNextAction() {
|
||||
return nextAction;
|
||||
}
|
||||
|
||||
public void setNextAction(String nextAction) {
|
||||
this.nextAction = nextAction;
|
||||
}
|
||||
|
||||
public OffsetDateTime getFollowUpTime() {
|
||||
return followUpTime;
|
||||
}
|
||||
|
||||
public void setFollowUpTime(OffsetDateTime followUpTime) {
|
||||
this.followUpTime = followUpTime;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
package com.unis.crm.dto.expansion;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.time.LocalDate;
|
||||
|
||||
public class CreateSalesExpansionRequest {
|
||||
|
||||
private Long id;
|
||||
|
||||
@NotBlank(message = "候选人姓名不能为空")
|
||||
@Size(max = 50, message = "候选人姓名不能超过50字符")
|
||||
private String candidateName;
|
||||
|
||||
private String mobile;
|
||||
private String email;
|
||||
private Long targetDeptId;
|
||||
private String industry;
|
||||
private String title;
|
||||
private String intentLevel;
|
||||
private String stage;
|
||||
private Boolean hasDesktopExp;
|
||||
private Boolean inProgress;
|
||||
private String employmentStatus;
|
||||
private LocalDate expectedJoinDate;
|
||||
private String remark;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getCandidateName() {
|
||||
return candidateName;
|
||||
}
|
||||
|
||||
public void setCandidateName(String candidateName) {
|
||||
this.candidateName = candidateName;
|
||||
}
|
||||
|
||||
public String getMobile() {
|
||||
return mobile;
|
||||
}
|
||||
|
||||
public void setMobile(String mobile) {
|
||||
this.mobile = mobile;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public Long getTargetDeptId() {
|
||||
return targetDeptId;
|
||||
}
|
||||
|
||||
public void setTargetDeptId(Long targetDeptId) {
|
||||
this.targetDeptId = targetDeptId;
|
||||
}
|
||||
|
||||
public String getIndustry() {
|
||||
return industry;
|
||||
}
|
||||
|
||||
public void setIndustry(String industry) {
|
||||
this.industry = industry;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getIntentLevel() {
|
||||
return intentLevel;
|
||||
}
|
||||
|
||||
public void setIntentLevel(String intentLevel) {
|
||||
this.intentLevel = intentLevel;
|
||||
}
|
||||
|
||||
public String getStage() {
|
||||
return stage;
|
||||
}
|
||||
|
||||
public void setStage(String stage) {
|
||||
this.stage = stage;
|
||||
}
|
||||
|
||||
public Boolean getHasDesktopExp() {
|
||||
return hasDesktopExp;
|
||||
}
|
||||
|
||||
public void setHasDesktopExp(Boolean hasDesktopExp) {
|
||||
this.hasDesktopExp = hasDesktopExp;
|
||||
}
|
||||
|
||||
public Boolean getInProgress() {
|
||||
return inProgress;
|
||||
}
|
||||
|
||||
public void setInProgress(Boolean inProgress) {
|
||||
this.inProgress = inProgress;
|
||||
}
|
||||
|
||||
public String getEmploymentStatus() {
|
||||
return employmentStatus;
|
||||
}
|
||||
|
||||
public void setEmploymentStatus(String employmentStatus) {
|
||||
this.employmentStatus = employmentStatus;
|
||||
}
|
||||
|
||||
public LocalDate getExpectedJoinDate() {
|
||||
return expectedJoinDate;
|
||||
}
|
||||
|
||||
public void setExpectedJoinDate(LocalDate expectedJoinDate) {
|
||||
this.expectedJoinDate = expectedJoinDate;
|
||||
}
|
||||
|
||||
public String getRemark() {
|
||||
return remark;
|
||||
}
|
||||
|
||||
public void setRemark(String remark) {
|
||||
this.remark = remark;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.unis.crm.dto.expansion;
|
||||
|
||||
public class DepartmentOptionDTO {
|
||||
|
||||
private Long id;
|
||||
private String name;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
package com.unis.crm.dto.expansion;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
public class ExpansionFollowUpDTO {
|
||||
|
||||
private Long id;
|
||||
private Long bizId;
|
||||
private String bizType;
|
||||
private OffsetDateTime followUpTime;
|
||||
private String date;
|
||||
private String type;
|
||||
private String content;
|
||||
private String user;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Long getBizId() {
|
||||
return bizId;
|
||||
}
|
||||
|
||||
public void setBizId(Long bizId) {
|
||||
this.bizId = bizId;
|
||||
}
|
||||
|
||||
public String getBizType() {
|
||||
return bizType;
|
||||
}
|
||||
|
||||
public void setBizType(String bizType) {
|
||||
this.bizType = bizType;
|
||||
}
|
||||
|
||||
public OffsetDateTime getFollowUpTime() {
|
||||
return followUpTime;
|
||||
}
|
||||
|
||||
public void setFollowUpTime(OffsetDateTime followUpTime) {
|
||||
this.followUpTime = followUpTime;
|
||||
}
|
||||
|
||||
public String getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
public void setDate(String date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public String getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(String user) {
|
||||
this.user = user;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.unis.crm.dto.expansion;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ExpansionMetaDTO {
|
||||
|
||||
private List<DepartmentOptionDTO> departments;
|
||||
|
||||
public ExpansionMetaDTO() {
|
||||
}
|
||||
|
||||
public ExpansionMetaDTO(List<DepartmentOptionDTO> departments) {
|
||||
this.departments = departments;
|
||||
}
|
||||
|
||||
public List<DepartmentOptionDTO> getDepartments() {
|
||||
return departments;
|
||||
}
|
||||
|
||||
public void setDepartments(List<DepartmentOptionDTO> departments) {
|
||||
this.departments = departments;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.unis.crm.dto.expansion;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ExpansionOverviewDTO {
|
||||
|
||||
private List<SalesExpansionItemDTO> salesItems;
|
||||
private List<ChannelExpansionItemDTO> channelItems;
|
||||
|
||||
public ExpansionOverviewDTO() {
|
||||
}
|
||||
|
||||
public ExpansionOverviewDTO(List<SalesExpansionItemDTO> salesItems, List<ChannelExpansionItemDTO> channelItems) {
|
||||
this.salesItems = salesItems;
|
||||
this.channelItems = channelItems;
|
||||
}
|
||||
|
||||
public List<SalesExpansionItemDTO> getSalesItems() {
|
||||
return salesItems;
|
||||
}
|
||||
|
||||
public void setSalesItems(List<SalesExpansionItemDTO> salesItems) {
|
||||
this.salesItems = salesItems;
|
||||
}
|
||||
|
||||
public List<ChannelExpansionItemDTO> getChannelItems() {
|
||||
return channelItems;
|
||||
}
|
||||
|
||||
public void setChannelItems(List<ChannelExpansionItemDTO> channelItems) {
|
||||
this.channelItems = channelItems;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
package com.unis.crm.dto.expansion;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SalesExpansionItemDTO {
|
||||
|
||||
private Long id;
|
||||
private String type;
|
||||
private String name;
|
||||
private String phone;
|
||||
private String email;
|
||||
private Long targetDeptId;
|
||||
private String dept;
|
||||
private String industry;
|
||||
private String title;
|
||||
private String intentLevel;
|
||||
private String intent;
|
||||
private String stageCode;
|
||||
private String stage;
|
||||
private Boolean hasExp;
|
||||
private Boolean inProgress;
|
||||
private Boolean active;
|
||||
private String employmentStatus;
|
||||
private String expectedJoinDate;
|
||||
private String notes;
|
||||
private List<ExpansionFollowUpDTO> followUps = new ArrayList<>();
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getPhone() {
|
||||
return phone;
|
||||
}
|
||||
|
||||
public void setPhone(String phone) {
|
||||
this.phone = phone;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public Long getTargetDeptId() {
|
||||
return targetDeptId;
|
||||
}
|
||||
|
||||
public void setTargetDeptId(Long targetDeptId) {
|
||||
this.targetDeptId = targetDeptId;
|
||||
}
|
||||
|
||||
public String getDept() {
|
||||
return dept;
|
||||
}
|
||||
|
||||
public void setDept(String dept) {
|
||||
this.dept = dept;
|
||||
}
|
||||
|
||||
public String getIndustry() {
|
||||
return industry;
|
||||
}
|
||||
|
||||
public void setIndustry(String industry) {
|
||||
this.industry = industry;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getIntentLevel() {
|
||||
return intentLevel;
|
||||
}
|
||||
|
||||
public void setIntentLevel(String intentLevel) {
|
||||
this.intentLevel = intentLevel;
|
||||
}
|
||||
|
||||
public String getIntent() {
|
||||
return intent;
|
||||
}
|
||||
|
||||
public void setIntent(String intent) {
|
||||
this.intent = intent;
|
||||
}
|
||||
|
||||
public String getStageCode() {
|
||||
return stageCode;
|
||||
}
|
||||
|
||||
public void setStageCode(String stageCode) {
|
||||
this.stageCode = stageCode;
|
||||
}
|
||||
|
||||
public String getStage() {
|
||||
return stage;
|
||||
}
|
||||
|
||||
public void setStage(String stage) {
|
||||
this.stage = stage;
|
||||
}
|
||||
|
||||
public Boolean getHasExp() {
|
||||
return hasExp;
|
||||
}
|
||||
|
||||
public void setHasExp(Boolean hasExp) {
|
||||
this.hasExp = hasExp;
|
||||
}
|
||||
|
||||
public Boolean getInProgress() {
|
||||
return inProgress;
|
||||
}
|
||||
|
||||
public void setInProgress(Boolean inProgress) {
|
||||
this.inProgress = inProgress;
|
||||
}
|
||||
|
||||
public Boolean getActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public void setActive(Boolean active) {
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
public String getEmploymentStatus() {
|
||||
return employmentStatus;
|
||||
}
|
||||
|
||||
public void setEmploymentStatus(String employmentStatus) {
|
||||
this.employmentStatus = employmentStatus;
|
||||
}
|
||||
|
||||
public String getExpectedJoinDate() {
|
||||
return expectedJoinDate;
|
||||
}
|
||||
|
||||
public void setExpectedJoinDate(String expectedJoinDate) {
|
||||
this.expectedJoinDate = expectedJoinDate;
|
||||
}
|
||||
|
||||
public String getNotes() {
|
||||
return notes;
|
||||
}
|
||||
|
||||
public void setNotes(String notes) {
|
||||
this.notes = notes;
|
||||
}
|
||||
|
||||
public List<ExpansionFollowUpDTO> getFollowUps() {
|
||||
return followUps;
|
||||
}
|
||||
|
||||
public void setFollowUps(List<ExpansionFollowUpDTO> followUps) {
|
||||
this.followUps = followUps;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
package com.unis.crm.dto.expansion;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
|
||||
public class UpdateChannelExpansionRequest {
|
||||
|
||||
@NotBlank(message = "渠道名称不能为空")
|
||||
@Size(max = 200, message = "渠道名称不能超过200字符")
|
||||
private String channelName;
|
||||
|
||||
private String province;
|
||||
private String industry;
|
||||
private BigDecimal annualRevenue;
|
||||
private Integer staffSize;
|
||||
private String contactName;
|
||||
private String contactTitle;
|
||||
private String contactMobile;
|
||||
private String stage;
|
||||
private Boolean landedFlag;
|
||||
private LocalDate expectedSignDate;
|
||||
private String remark;
|
||||
|
||||
public String getChannelName() {
|
||||
return channelName;
|
||||
}
|
||||
|
||||
public void setChannelName(String channelName) {
|
||||
this.channelName = channelName;
|
||||
}
|
||||
|
||||
public String getProvince() {
|
||||
return province;
|
||||
}
|
||||
|
||||
public void setProvince(String province) {
|
||||
this.province = province;
|
||||
}
|
||||
|
||||
public String getIndustry() {
|
||||
return industry;
|
||||
}
|
||||
|
||||
public void setIndustry(String industry) {
|
||||
this.industry = industry;
|
||||
}
|
||||
|
||||
public BigDecimal getAnnualRevenue() {
|
||||
return annualRevenue;
|
||||
}
|
||||
|
||||
public void setAnnualRevenue(BigDecimal annualRevenue) {
|
||||
this.annualRevenue = annualRevenue;
|
||||
}
|
||||
|
||||
public Integer getStaffSize() {
|
||||
return staffSize;
|
||||
}
|
||||
|
||||
public void setStaffSize(Integer staffSize) {
|
||||
this.staffSize = staffSize;
|
||||
}
|
||||
|
||||
public String getContactName() {
|
||||
return contactName;
|
||||
}
|
||||
|
||||
public void setContactName(String contactName) {
|
||||
this.contactName = contactName;
|
||||
}
|
||||
|
||||
public String getContactTitle() {
|
||||
return contactTitle;
|
||||
}
|
||||
|
||||
public void setContactTitle(String contactTitle) {
|
||||
this.contactTitle = contactTitle;
|
||||
}
|
||||
|
||||
public String getContactMobile() {
|
||||
return contactMobile;
|
||||
}
|
||||
|
||||
public void setContactMobile(String contactMobile) {
|
||||
this.contactMobile = contactMobile;
|
||||
}
|
||||
|
||||
public String getStage() {
|
||||
return stage;
|
||||
}
|
||||
|
||||
public void setStage(String stage) {
|
||||
this.stage = stage;
|
||||
}
|
||||
|
||||
public Boolean getLandedFlag() {
|
||||
return landedFlag;
|
||||
}
|
||||
|
||||
public void setLandedFlag(Boolean landedFlag) {
|
||||
this.landedFlag = landedFlag;
|
||||
}
|
||||
|
||||
public LocalDate getExpectedSignDate() {
|
||||
return expectedSignDate;
|
||||
}
|
||||
|
||||
public void setExpectedSignDate(LocalDate expectedSignDate) {
|
||||
this.expectedSignDate = expectedSignDate;
|
||||
}
|
||||
|
||||
public String getRemark() {
|
||||
return remark;
|
||||
}
|
||||
|
||||
public void setRemark(String remark) {
|
||||
this.remark = remark;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
package com.unis.crm.dto.expansion;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.time.LocalDate;
|
||||
|
||||
public class UpdateSalesExpansionRequest {
|
||||
|
||||
@NotBlank(message = "候选人姓名不能为空")
|
||||
@Size(max = 50, message = "候选人姓名不能超过50字符")
|
||||
private String candidateName;
|
||||
|
||||
private String mobile;
|
||||
private String email;
|
||||
private Long targetDeptId;
|
||||
private String industry;
|
||||
private String title;
|
||||
private String intentLevel;
|
||||
private String stage;
|
||||
private Boolean hasDesktopExp;
|
||||
private Boolean inProgress;
|
||||
private String employmentStatus;
|
||||
private LocalDate expectedJoinDate;
|
||||
private String remark;
|
||||
|
||||
public String getCandidateName() {
|
||||
return candidateName;
|
||||
}
|
||||
|
||||
public void setCandidateName(String candidateName) {
|
||||
this.candidateName = candidateName;
|
||||
}
|
||||
|
||||
public String getMobile() {
|
||||
return mobile;
|
||||
}
|
||||
|
||||
public void setMobile(String mobile) {
|
||||
this.mobile = mobile;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public Long getTargetDeptId() {
|
||||
return targetDeptId;
|
||||
}
|
||||
|
||||
public void setTargetDeptId(Long targetDeptId) {
|
||||
this.targetDeptId = targetDeptId;
|
||||
}
|
||||
|
||||
public String getIndustry() {
|
||||
return industry;
|
||||
}
|
||||
|
||||
public void setIndustry(String industry) {
|
||||
this.industry = industry;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getIntentLevel() {
|
||||
return intentLevel;
|
||||
}
|
||||
|
||||
public void setIntentLevel(String intentLevel) {
|
||||
this.intentLevel = intentLevel;
|
||||
}
|
||||
|
||||
public String getStage() {
|
||||
return stage;
|
||||
}
|
||||
|
||||
public void setStage(String stage) {
|
||||
this.stage = stage;
|
||||
}
|
||||
|
||||
public Boolean getHasDesktopExp() {
|
||||
return hasDesktopExp;
|
||||
}
|
||||
|
||||
public void setHasDesktopExp(Boolean hasDesktopExp) {
|
||||
this.hasDesktopExp = hasDesktopExp;
|
||||
}
|
||||
|
||||
public Boolean getInProgress() {
|
||||
return inProgress;
|
||||
}
|
||||
|
||||
public void setInProgress(Boolean inProgress) {
|
||||
this.inProgress = inProgress;
|
||||
}
|
||||
|
||||
public String getEmploymentStatus() {
|
||||
return employmentStatus;
|
||||
}
|
||||
|
||||
public void setEmploymentStatus(String employmentStatus) {
|
||||
this.employmentStatus = employmentStatus;
|
||||
}
|
||||
|
||||
public LocalDate getExpectedJoinDate() {
|
||||
return expectedJoinDate;
|
||||
}
|
||||
|
||||
public void setExpectedJoinDate(LocalDate expectedJoinDate) {
|
||||
this.expectedJoinDate = expectedJoinDate;
|
||||
}
|
||||
|
||||
public String getRemark() {
|
||||
return remark;
|
||||
}
|
||||
|
||||
public void setRemark(String remark) {
|
||||
this.remark = remark;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package com.unis.crm.dto.opportunity;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
public class CreateOpportunityFollowUpRequest {
|
||||
|
||||
@NotBlank(message = "跟进类型不能为空")
|
||||
private String followUpType;
|
||||
|
||||
@NotBlank(message = "跟进内容不能为空")
|
||||
private String content;
|
||||
|
||||
private String nextAction;
|
||||
|
||||
@NotNull(message = "跟进时间不能为空")
|
||||
private OffsetDateTime followUpTime;
|
||||
|
||||
public String getFollowUpType() {
|
||||
return followUpType;
|
||||
}
|
||||
|
||||
public void setFollowUpType(String followUpType) {
|
||||
this.followUpType = followUpType;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public String getNextAction() {
|
||||
return nextAction;
|
||||
}
|
||||
|
||||
public void setNextAction(String nextAction) {
|
||||
this.nextAction = nextAction;
|
||||
}
|
||||
|
||||
public OffsetDateTime getFollowUpTime() {
|
||||
return followUpTime;
|
||||
}
|
||||
|
||||
public void setFollowUpTime(OffsetDateTime followUpTime) {
|
||||
this.followUpTime = followUpTime;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
package com.unis.crm.dto.opportunity;
|
||||
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
|
||||
public class CreateOpportunityRequest {
|
||||
|
||||
private Long id;
|
||||
|
||||
@NotBlank(message = "商机名称不能为空")
|
||||
@Size(max = 200, message = "商机名称不能超过200字符")
|
||||
private String opportunityName;
|
||||
|
||||
@NotBlank(message = "客户名称不能为空")
|
||||
@Size(max = 200, message = "客户名称不能超过200字符")
|
||||
private String customerName;
|
||||
|
||||
@NotNull(message = "商机金额不能为空")
|
||||
private BigDecimal amount;
|
||||
|
||||
private LocalDate expectedCloseDate;
|
||||
|
||||
@NotNull(message = "把握度不能为空")
|
||||
@Min(value = 0, message = "把握度不能低于0")
|
||||
@Max(value = 100, message = "把握度不能高于100")
|
||||
private Integer confidencePct;
|
||||
|
||||
private String stage;
|
||||
private String opportunityType;
|
||||
private String productType;
|
||||
private String source;
|
||||
private Boolean pushedToOms;
|
||||
private String description;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getOpportunityName() {
|
||||
return opportunityName;
|
||||
}
|
||||
|
||||
public void setOpportunityName(String opportunityName) {
|
||||
this.opportunityName = opportunityName;
|
||||
}
|
||||
|
||||
public String getCustomerName() {
|
||||
return customerName;
|
||||
}
|
||||
|
||||
public void setCustomerName(String customerName) {
|
||||
this.customerName = customerName;
|
||||
}
|
||||
|
||||
public BigDecimal getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public void setAmount(BigDecimal amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public LocalDate getExpectedCloseDate() {
|
||||
return expectedCloseDate;
|
||||
}
|
||||
|
||||
public void setExpectedCloseDate(LocalDate expectedCloseDate) {
|
||||
this.expectedCloseDate = expectedCloseDate;
|
||||
}
|
||||
|
||||
public Integer getConfidencePct() {
|
||||
return confidencePct;
|
||||
}
|
||||
|
||||
public void setConfidencePct(Integer confidencePct) {
|
||||
this.confidencePct = confidencePct;
|
||||
}
|
||||
|
||||
public String getStage() {
|
||||
return stage;
|
||||
}
|
||||
|
||||
public void setStage(String stage) {
|
||||
this.stage = stage;
|
||||
}
|
||||
|
||||
public String getOpportunityType() {
|
||||
return opportunityType;
|
||||
}
|
||||
|
||||
public void setOpportunityType(String opportunityType) {
|
||||
this.opportunityType = opportunityType;
|
||||
}
|
||||
|
||||
public String getProductType() {
|
||||
return productType;
|
||||
}
|
||||
|
||||
public void setProductType(String productType) {
|
||||
this.productType = productType;
|
||||
}
|
||||
|
||||
public String getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
public void setSource(String source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
public Boolean getPushedToOms() {
|
||||
return pushedToOms;
|
||||
}
|
||||
|
||||
public void setPushedToOms(Boolean pushedToOms) {
|
||||
this.pushedToOms = pushedToOms;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package com.unis.crm.dto.opportunity;
|
||||
|
||||
public class OpportunityFollowUpDTO {
|
||||
|
||||
private Long id;
|
||||
private Long opportunityId;
|
||||
private String date;
|
||||
private String type;
|
||||
private String content;
|
||||
private String user;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Long getOpportunityId() {
|
||||
return opportunityId;
|
||||
}
|
||||
|
||||
public void setOpportunityId(Long opportunityId) {
|
||||
this.opportunityId = opportunityId;
|
||||
}
|
||||
|
||||
public String getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
public void setDate(String date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public String getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(String user) {
|
||||
this.user = user;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
package com.unis.crm.dto.opportunity;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class OpportunityItemDTO {
|
||||
|
||||
private Long id;
|
||||
private String code;
|
||||
private String name;
|
||||
private String client;
|
||||
private String owner;
|
||||
private BigDecimal amount;
|
||||
private String date;
|
||||
private Integer confidence;
|
||||
private String stage;
|
||||
private String type;
|
||||
private Boolean pushedToOms;
|
||||
private String product;
|
||||
private String source;
|
||||
private String notes;
|
||||
private List<OpportunityFollowUpDTO> followUps = new ArrayList<>();
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public void setClient(String client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public String getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public void setOwner(String owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public BigDecimal getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public void setAmount(BigDecimal amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public String getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
public void setDate(String date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
public Integer getConfidence() {
|
||||
return confidence;
|
||||
}
|
||||
|
||||
public void setConfidence(Integer confidence) {
|
||||
this.confidence = confidence;
|
||||
}
|
||||
|
||||
public String getStage() {
|
||||
return stage;
|
||||
}
|
||||
|
||||
public void setStage(String stage) {
|
||||
this.stage = stage;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Boolean getPushedToOms() {
|
||||
return pushedToOms;
|
||||
}
|
||||
|
||||
public void setPushedToOms(Boolean pushedToOms) {
|
||||
this.pushedToOms = pushedToOms;
|
||||
}
|
||||
|
||||
public String getProduct() {
|
||||
return product;
|
||||
}
|
||||
|
||||
public void setProduct(String product) {
|
||||
this.product = product;
|
||||
}
|
||||
|
||||
public String getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
public void setSource(String source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
public String getNotes() {
|
||||
return notes;
|
||||
}
|
||||
|
||||
public void setNotes(String notes) {
|
||||
this.notes = notes;
|
||||
}
|
||||
|
||||
public List<OpportunityFollowUpDTO> getFollowUps() {
|
||||
return followUps;
|
||||
}
|
||||
|
||||
public void setFollowUps(List<OpportunityFollowUpDTO> followUps) {
|
||||
this.followUps = followUps;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.unis.crm.dto.opportunity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class OpportunityOverviewDTO {
|
||||
|
||||
private List<OpportunityItemDTO> items;
|
||||
|
||||
public OpportunityOverviewDTO() {
|
||||
}
|
||||
|
||||
public OpportunityOverviewDTO(List<OpportunityItemDTO> items) {
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
public List<OpportunityItemDTO> getItems() {
|
||||
return items;
|
||||
}
|
||||
|
||||
public void setItems(List<OpportunityItemDTO> items) {
|
||||
this.items = items;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package com.unis.crm.dto.work;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class CreateWorkCheckInRequest {
|
||||
|
||||
@NotBlank(message = "打卡地点不能为空")
|
||||
@Size(max = 500, message = "打卡地点不能超过500字符")
|
||||
private String locationText;
|
||||
|
||||
@Size(max = 500, message = "备注不能超过500字符")
|
||||
private String remark;
|
||||
|
||||
private BigDecimal longitude;
|
||||
private BigDecimal latitude;
|
||||
|
||||
public String getLocationText() {
|
||||
return locationText;
|
||||
}
|
||||
|
||||
public void setLocationText(String locationText) {
|
||||
this.locationText = locationText;
|
||||
}
|
||||
|
||||
public String getRemark() {
|
||||
return remark;
|
||||
}
|
||||
|
||||
public void setRemark(String remark) {
|
||||
this.remark = remark;
|
||||
}
|
||||
|
||||
public BigDecimal getLongitude() {
|
||||
return longitude;
|
||||
}
|
||||
|
||||
public void setLongitude(BigDecimal longitude) {
|
||||
this.longitude = longitude;
|
||||
}
|
||||
|
||||
public BigDecimal getLatitude() {
|
||||
return latitude;
|
||||
}
|
||||
|
||||
public void setLatitude(BigDecimal latitude) {
|
||||
this.latitude = latitude;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package com.unis.crm.dto.work;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
public class CreateWorkDailyReportRequest {
|
||||
|
||||
@NotBlank(message = "今日工作内容不能为空")
|
||||
@Size(max = 4000, message = "今日工作内容不能超过4000字符")
|
||||
private String workContent;
|
||||
|
||||
@NotBlank(message = "明日工作计划不能为空")
|
||||
@Size(max = 4000, message = "明日工作计划不能超过4000字符")
|
||||
private String tomorrowPlan;
|
||||
|
||||
@Size(max = 50, message = "来源类型不能超过50字符")
|
||||
private String sourceType;
|
||||
|
||||
public String getWorkContent() {
|
||||
return workContent;
|
||||
}
|
||||
|
||||
public void setWorkContent(String workContent) {
|
||||
this.workContent = workContent;
|
||||
}
|
||||
|
||||
public String getTomorrowPlan() {
|
||||
return tomorrowPlan;
|
||||
}
|
||||
|
||||
public void setTomorrowPlan(String tomorrowPlan) {
|
||||
this.tomorrowPlan = tomorrowPlan;
|
||||
}
|
||||
|
||||
public String getSourceType() {
|
||||
return sourceType;
|
||||
}
|
||||
|
||||
public void setSourceType(String sourceType) {
|
||||
this.sourceType = sourceType;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
package com.unis.crm.dto.work;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class WorkCheckInDTO {
|
||||
|
||||
private Long id;
|
||||
private String date;
|
||||
private String time;
|
||||
private String locationText;
|
||||
private String remark;
|
||||
private String status;
|
||||
private BigDecimal longitude;
|
||||
private BigDecimal latitude;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
public void setDate(String date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
public String getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void setTime(String time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public String getLocationText() {
|
||||
return locationText;
|
||||
}
|
||||
|
||||
public void setLocationText(String locationText) {
|
||||
this.locationText = locationText;
|
||||
}
|
||||
|
||||
public String getRemark() {
|
||||
return remark;
|
||||
}
|
||||
|
||||
public void setRemark(String remark) {
|
||||
this.remark = remark;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public BigDecimal getLongitude() {
|
||||
return longitude;
|
||||
}
|
||||
|
||||
public void setLongitude(BigDecimal longitude) {
|
||||
this.longitude = longitude;
|
||||
}
|
||||
|
||||
public BigDecimal getLatitude() {
|
||||
return latitude;
|
||||
}
|
||||
|
||||
public void setLatitude(BigDecimal latitude) {
|
||||
this.latitude = latitude;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
package com.unis.crm.dto.work;
|
||||
|
||||
public class WorkDailyReportDTO {
|
||||
|
||||
private Long id;
|
||||
private String date;
|
||||
private String submitTime;
|
||||
private String workContent;
|
||||
private String tomorrowPlan;
|
||||
private String sourceType;
|
||||
private String status;
|
||||
private Integer score;
|
||||
private String comment;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
public void setDate(String date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
public String getSubmitTime() {
|
||||
return submitTime;
|
||||
}
|
||||
|
||||
public void setSubmitTime(String submitTime) {
|
||||
this.submitTime = submitTime;
|
||||
}
|
||||
|
||||
public String getWorkContent() {
|
||||
return workContent;
|
||||
}
|
||||
|
||||
public void setWorkContent(String workContent) {
|
||||
this.workContent = workContent;
|
||||
}
|
||||
|
||||
public String getTomorrowPlan() {
|
||||
return tomorrowPlan;
|
||||
}
|
||||
|
||||
public void setTomorrowPlan(String tomorrowPlan) {
|
||||
this.tomorrowPlan = tomorrowPlan;
|
||||
}
|
||||
|
||||
public String getSourceType() {
|
||||
return sourceType;
|
||||
}
|
||||
|
||||
public void setSourceType(String sourceType) {
|
||||
this.sourceType = sourceType;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public Integer getScore() {
|
||||
return score;
|
||||
}
|
||||
|
||||
public void setScore(Integer score) {
|
||||
this.score = score;
|
||||
}
|
||||
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
public void setComment(String comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package com.unis.crm.dto.work;
|
||||
|
||||
public class WorkHistoryItemDTO {
|
||||
|
||||
private Long id;
|
||||
private String type;
|
||||
private String date;
|
||||
private String time;
|
||||
private String content;
|
||||
private String status;
|
||||
private Integer score;
|
||||
private String comment;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
public void setDate(String date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
public String getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void setTime(String time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public Integer getScore() {
|
||||
return score;
|
||||
}
|
||||
|
||||
public void setScore(Integer score) {
|
||||
this.score = score;
|
||||
}
|
||||
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
public void setComment(String comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package com.unis.crm.dto.work;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class WorkOverviewDTO {
|
||||
|
||||
private WorkCheckInDTO todayCheckIn;
|
||||
private WorkDailyReportDTO todayReport;
|
||||
private String suggestedWorkContent;
|
||||
private List<WorkHistoryItemDTO> history;
|
||||
|
||||
public WorkOverviewDTO() {
|
||||
}
|
||||
|
||||
public WorkOverviewDTO(
|
||||
WorkCheckInDTO todayCheckIn,
|
||||
WorkDailyReportDTO todayReport,
|
||||
String suggestedWorkContent,
|
||||
List<WorkHistoryItemDTO> history) {
|
||||
this.todayCheckIn = todayCheckIn;
|
||||
this.todayReport = todayReport;
|
||||
this.suggestedWorkContent = suggestedWorkContent;
|
||||
this.history = history;
|
||||
}
|
||||
|
||||
public WorkCheckInDTO getTodayCheckIn() {
|
||||
return todayCheckIn;
|
||||
}
|
||||
|
||||
public void setTodayCheckIn(WorkCheckInDTO todayCheckIn) {
|
||||
this.todayCheckIn = todayCheckIn;
|
||||
}
|
||||
|
||||
public WorkDailyReportDTO getTodayReport() {
|
||||
return todayReport;
|
||||
}
|
||||
|
||||
public void setTodayReport(WorkDailyReportDTO todayReport) {
|
||||
this.todayReport = todayReport;
|
||||
}
|
||||
|
||||
public String getSuggestedWorkContent() {
|
||||
return suggestedWorkContent;
|
||||
}
|
||||
|
||||
public void setSuggestedWorkContent(String suggestedWorkContent) {
|
||||
this.suggestedWorkContent = suggestedWorkContent;
|
||||
}
|
||||
|
||||
public List<WorkHistoryItemDTO> getHistory() {
|
||||
return history;
|
||||
}
|
||||
|
||||
public void setHistory(List<WorkHistoryItemDTO> history) {
|
||||
this.history = history;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.unis.crm.mapper;
|
||||
|
||||
import com.unis.crm.dto.dashboard.DashboardActivityDTO;
|
||||
import com.unis.crm.dto.dashboard.DashboardStatDTO;
|
||||
import com.unis.crm.dto.dashboard.DashboardTodoDTO;
|
||||
import com.unis.crm.dto.dashboard.UserWelcomeDTO;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@Mapper
|
||||
public interface DashboardMapper {
|
||||
|
||||
Long selectDefaultUserId();
|
||||
|
||||
UserWelcomeDTO selectUserWelcome(@Param("userId") Long userId);
|
||||
|
||||
List<DashboardStatDTO> selectDashboardStats(@Param("userId") Long userId);
|
||||
|
||||
List<DashboardTodoDTO> selectPendingTodos(@Param("userId") Long userId);
|
||||
|
||||
List<DashboardActivityDTO> selectLatestActivities(@Param("userId") Long userId);
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.unis.crm.mapper;
|
||||
|
||||
import com.unis.crm.dto.expansion.ChannelExpansionItemDTO;
|
||||
import com.unis.crm.dto.expansion.CreateChannelExpansionRequest;
|
||||
import com.unis.crm.dto.expansion.CreateExpansionFollowUpRequest;
|
||||
import com.unis.crm.dto.expansion.CreateSalesExpansionRequest;
|
||||
import com.unis.crm.dto.expansion.DepartmentOptionDTO;
|
||||
import com.unis.crm.dto.expansion.ExpansionFollowUpDTO;
|
||||
import com.unis.crm.dto.expansion.SalesExpansionItemDTO;
|
||||
import com.unis.crm.dto.expansion.UpdateChannelExpansionRequest;
|
||||
import com.unis.crm.dto.expansion.UpdateSalesExpansionRequest;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@Mapper
|
||||
public interface ExpansionMapper {
|
||||
|
||||
List<DepartmentOptionDTO> selectDepartments();
|
||||
|
||||
List<SalesExpansionItemDTO> selectSalesExpansions(@Param("userId") Long userId, @Param("keyword") String keyword);
|
||||
|
||||
List<ChannelExpansionItemDTO> selectChannelExpansions(@Param("userId") Long userId, @Param("keyword") String keyword);
|
||||
|
||||
List<ExpansionFollowUpDTO> selectSalesFollowUps(@Param("userId") Long userId, @Param("bizIds") List<Long> bizIds);
|
||||
|
||||
List<ExpansionFollowUpDTO> selectChannelFollowUps(@Param("userId") Long userId, @Param("bizIds") List<Long> bizIds);
|
||||
|
||||
int insertSalesExpansion(@Param("userId") Long userId, @Param("request") CreateSalesExpansionRequest request);
|
||||
|
||||
int insertChannelExpansion(@Param("userId") Long userId, @Param("request") CreateChannelExpansionRequest request);
|
||||
|
||||
int updateSalesExpansion(@Param("userId") Long userId, @Param("id") Long id, @Param("request") UpdateSalesExpansionRequest request);
|
||||
|
||||
int updateChannelExpansion(@Param("userId") Long userId, @Param("id") Long id, @Param("request") UpdateChannelExpansionRequest request);
|
||||
|
||||
int countOwnedSalesExpansion(@Param("userId") Long userId, @Param("id") Long id);
|
||||
|
||||
int countOwnedChannelExpansion(@Param("userId") Long userId, @Param("id") Long id);
|
||||
|
||||
int insertExpansionFollowUp(
|
||||
@Param("bizType") String bizType,
|
||||
@Param("bizId") Long bizId,
|
||||
@Param("userId") Long userId,
|
||||
@Param("request") CreateExpansionFollowUpRequest request);
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package com.unis.crm.mapper;
|
||||
|
||||
import com.unis.crm.dto.opportunity.CreateOpportunityFollowUpRequest;
|
||||
import com.unis.crm.dto.opportunity.CreateOpportunityRequest;
|
||||
import com.unis.crm.dto.opportunity.OpportunityFollowUpDTO;
|
||||
import com.unis.crm.dto.opportunity.OpportunityItemDTO;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@Mapper
|
||||
public interface OpportunityMapper {
|
||||
|
||||
List<OpportunityItemDTO> selectOpportunities(
|
||||
@Param("userId") Long userId,
|
||||
@Param("keyword") String keyword,
|
||||
@Param("stage") String stage);
|
||||
|
||||
List<OpportunityFollowUpDTO> selectOpportunityFollowUps(
|
||||
@Param("userId") Long userId,
|
||||
@Param("opportunityIds") List<Long> opportunityIds);
|
||||
|
||||
Long selectOwnedCustomerIdByName(@Param("userId") Long userId, @Param("customerName") String customerName);
|
||||
|
||||
int insertCustomer(
|
||||
@Param("id") Long id,
|
||||
@Param("userId") Long userId,
|
||||
@Param("customerName") String customerName,
|
||||
@Param("source") String source);
|
||||
|
||||
int insertOpportunity(
|
||||
@Param("userId") Long userId,
|
||||
@Param("customerId") Long customerId,
|
||||
@Param("request") CreateOpportunityRequest request);
|
||||
|
||||
int countOwnedOpportunity(@Param("userId") Long userId, @Param("id") Long id);
|
||||
|
||||
int updateOpportunity(
|
||||
@Param("userId") Long userId,
|
||||
@Param("opportunityId") Long opportunityId,
|
||||
@Param("customerId") Long customerId,
|
||||
@Param("request") CreateOpportunityRequest request);
|
||||
|
||||
int insertOpportunityFollowUp(
|
||||
@Param("userId") Long userId,
|
||||
@Param("opportunityId") Long opportunityId,
|
||||
@Param("request") CreateOpportunityFollowUpRequest request);
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.unis.crm.mapper;
|
||||
|
||||
import com.unis.crm.dto.work.CreateWorkCheckInRequest;
|
||||
import com.unis.crm.dto.work.CreateWorkDailyReportRequest;
|
||||
import com.unis.crm.dto.work.WorkCheckInDTO;
|
||||
import com.unis.crm.dto.work.WorkDailyReportDTO;
|
||||
import com.unis.crm.dto.work.WorkHistoryItemDTO;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@Mapper
|
||||
public interface WorkMapper {
|
||||
|
||||
WorkCheckInDTO selectTodayCheckIn(@Param("userId") Long userId);
|
||||
|
||||
WorkDailyReportDTO selectTodayReport(@Param("userId") Long userId);
|
||||
|
||||
List<String> selectTodayWorkContentLines(@Param("userId") Long userId);
|
||||
|
||||
List<WorkHistoryItemDTO> selectHistory(@Param("userId") Long userId);
|
||||
|
||||
Long selectTodayCheckInId(@Param("userId") Long userId);
|
||||
|
||||
int insertCheckIn(@Param("userId") Long userId, @Param("request") CreateWorkCheckInRequest request);
|
||||
|
||||
int updateCheckIn(@Param("checkInId") Long checkInId, @Param("request") CreateWorkCheckInRequest request);
|
||||
|
||||
Long selectTodayReportId(@Param("userId") Long userId);
|
||||
|
||||
int insertDailyReport(@Param("userId") Long userId, @Param("request") CreateWorkDailyReportRequest request);
|
||||
|
||||
int updateDailyReport(@Param("reportId") Long reportId, @Param("request") CreateWorkDailyReportRequest request);
|
||||
|
||||
Long selectTodoIdByBiz(@Param("userId") Long userId, @Param("bizType") String bizType, @Param("bizId") Long bizId);
|
||||
|
||||
int insertTodo(
|
||||
@Param("todoId") Long todoId,
|
||||
@Param("userId") Long userId,
|
||||
@Param("title") String title,
|
||||
@Param("bizType") String bizType,
|
||||
@Param("bizId") Long bizId);
|
||||
|
||||
int updateTodo(
|
||||
@Param("todoId") Long todoId,
|
||||
@Param("title") String title);
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.unis.crm.service;
|
||||
|
||||
import com.unis.crm.dto.dashboard.DashboardHomeDTO;
|
||||
|
||||
public interface DashboardService {
|
||||
|
||||
DashboardHomeDTO getHome(Long userId);
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.unis.crm.service;
|
||||
|
||||
import com.unis.crm.dto.expansion.CreateChannelExpansionRequest;
|
||||
import com.unis.crm.dto.expansion.CreateExpansionFollowUpRequest;
|
||||
import com.unis.crm.dto.expansion.CreateSalesExpansionRequest;
|
||||
import com.unis.crm.dto.expansion.ExpansionMetaDTO;
|
||||
import com.unis.crm.dto.expansion.ExpansionOverviewDTO;
|
||||
import com.unis.crm.dto.expansion.UpdateChannelExpansionRequest;
|
||||
import com.unis.crm.dto.expansion.UpdateSalesExpansionRequest;
|
||||
|
||||
public interface ExpansionService {
|
||||
|
||||
ExpansionMetaDTO getMeta();
|
||||
|
||||
ExpansionOverviewDTO getOverview(Long userId, String keyword);
|
||||
|
||||
Long createSalesExpansion(Long userId, CreateSalesExpansionRequest request);
|
||||
|
||||
Long createChannelExpansion(Long userId, CreateChannelExpansionRequest request);
|
||||
|
||||
void updateSalesExpansion(Long userId, Long id, UpdateSalesExpansionRequest request);
|
||||
|
||||
void updateChannelExpansion(Long userId, Long id, UpdateChannelExpansionRequest request);
|
||||
|
||||
Long createFollowUp(Long userId, String bizType, Long bizId, CreateExpansionFollowUpRequest request);
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.unis.crm.service;
|
||||
|
||||
import com.unis.crm.dto.opportunity.CreateOpportunityFollowUpRequest;
|
||||
import com.unis.crm.dto.opportunity.CreateOpportunityRequest;
|
||||
import com.unis.crm.dto.opportunity.OpportunityOverviewDTO;
|
||||
|
||||
public interface OpportunityService {
|
||||
|
||||
OpportunityOverviewDTO getOverview(Long userId, String keyword, String stage);
|
||||
|
||||
Long createOpportunity(Long userId, CreateOpportunityRequest request);
|
||||
|
||||
Long updateOpportunity(Long userId, Long opportunityId, CreateOpportunityRequest request);
|
||||
|
||||
Long createFollowUp(Long userId, Long opportunityId, CreateOpportunityFollowUpRequest request);
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.unis.crm.service;
|
||||
|
||||
import com.unis.crm.dto.work.CreateWorkCheckInRequest;
|
||||
import com.unis.crm.dto.work.CreateWorkDailyReportRequest;
|
||||
import com.unis.crm.dto.work.WorkOverviewDTO;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public interface WorkService {
|
||||
|
||||
WorkOverviewDTO getOverview(Long userId);
|
||||
|
||||
Long saveCheckIn(Long userId, CreateWorkCheckInRequest request);
|
||||
|
||||
Long saveDailyReport(Long userId, CreateWorkDailyReportRequest request);
|
||||
|
||||
String resolveLocationName(BigDecimal latitude, BigDecimal longitude);
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
package com.unis.crm.service.impl;
|
||||
|
||||
import com.unis.crm.common.BusinessException;
|
||||
import com.unis.crm.dto.dashboard.DashboardActivityDTO;
|
||||
import com.unis.crm.dto.dashboard.DashboardHomeDTO;
|
||||
import com.unis.crm.dto.dashboard.DashboardStatDTO;
|
||||
import com.unis.crm.dto.dashboard.DashboardTodoDTO;
|
||||
import com.unis.crm.dto.dashboard.UserWelcomeDTO;
|
||||
import com.unis.crm.mapper.DashboardMapper;
|
||||
import com.unis.crm.service.DashboardService;
|
||||
import java.time.LocalDate;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class DashboardServiceImpl implements DashboardService {
|
||||
|
||||
private final DashboardMapper dashboardMapper;
|
||||
|
||||
public DashboardServiceImpl(DashboardMapper dashboardMapper) {
|
||||
this.dashboardMapper = dashboardMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DashboardHomeDTO getHome(Long userId) {
|
||||
if (userId == null) {
|
||||
throw new BusinessException("未获取到当前登录用户,禁止查询他人数据");
|
||||
}
|
||||
|
||||
UserWelcomeDTO user = dashboardMapper.selectUserWelcome(userId);
|
||||
if (user == null) {
|
||||
throw new BusinessException("未找到当前用户对应数据");
|
||||
}
|
||||
|
||||
List<DashboardStatDTO> stats = dashboardMapper.selectDashboardStats(userId);
|
||||
List<DashboardTodoDTO> todos = dashboardMapper.selectPendingTodos(userId);
|
||||
List<DashboardActivityDTO> activities = dashboardMapper.selectLatestActivities(userId);
|
||||
enrichActivityTimeText(activities);
|
||||
|
||||
long onboardingDays = 0;
|
||||
LocalDate hireDate = user.getHireDate();
|
||||
if (hireDate != null) {
|
||||
onboardingDays = ChronoUnit.DAYS.between(hireDate, LocalDate.now()) + 1;
|
||||
}
|
||||
|
||||
return new DashboardHomeDTO(
|
||||
user.getUserId(),
|
||||
user.getRealName(),
|
||||
user.getJobTitle(),
|
||||
user.getDeptName(),
|
||||
onboardingDays,
|
||||
stats,
|
||||
todos,
|
||||
activities
|
||||
);
|
||||
}
|
||||
|
||||
private void enrichActivityTimeText(List<DashboardActivityDTO> activities) {
|
||||
if (activities == null || activities.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
OffsetDateTime now = OffsetDateTime.now();
|
||||
for (DashboardActivityDTO activity : activities) {
|
||||
activity.setTimeText(formatRelativeTime(activity.getCreatedAt(), now));
|
||||
}
|
||||
}
|
||||
|
||||
private String formatRelativeTime(OffsetDateTime createdAt, OffsetDateTime now) {
|
||||
if (createdAt == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
long minutes = ChronoUnit.MINUTES.between(createdAt, now);
|
||||
if (minutes < 1) {
|
||||
return "刚刚";
|
||||
}
|
||||
if (minutes < 60) {
|
||||
return minutes + "分钟前";
|
||||
}
|
||||
|
||||
long hours = ChronoUnit.HOURS.between(createdAt, now);
|
||||
if (hours < 24) {
|
||||
return hours + "小时前";
|
||||
}
|
||||
|
||||
LocalDate createdDate = createdAt.atZoneSameInstant(ZoneId.systemDefault()).toLocalDate();
|
||||
LocalDate today = now.atZoneSameInstant(ZoneId.systemDefault()).toLocalDate();
|
||||
long days = ChronoUnit.DAYS.between(createdDate, today);
|
||||
if (days == 1) {
|
||||
return "昨天";
|
||||
}
|
||||
if (days < 7) {
|
||||
return days + "天前";
|
||||
}
|
||||
|
||||
return createdAt.toLocalDate().toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,248 @@
|
|||
package com.unis.crm.service.impl;
|
||||
|
||||
import com.unis.crm.common.BusinessException;
|
||||
import com.unis.crm.dto.expansion.ChannelExpansionItemDTO;
|
||||
import com.unis.crm.dto.expansion.CreateChannelExpansionRequest;
|
||||
import com.unis.crm.dto.expansion.CreateExpansionFollowUpRequest;
|
||||
import com.unis.crm.dto.expansion.CreateSalesExpansionRequest;
|
||||
import com.unis.crm.dto.expansion.ExpansionMetaDTO;
|
||||
import com.unis.crm.dto.expansion.ExpansionFollowUpDTO;
|
||||
import com.unis.crm.dto.expansion.ExpansionOverviewDTO;
|
||||
import com.unis.crm.dto.expansion.SalesExpansionItemDTO;
|
||||
import com.unis.crm.dto.expansion.UpdateChannelExpansionRequest;
|
||||
import com.unis.crm.dto.expansion.UpdateSalesExpansionRequest;
|
||||
import com.unis.crm.mapper.ExpansionMapper;
|
||||
import com.unis.crm.service.ExpansionService;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class ExpansionServiceImpl implements ExpansionService {
|
||||
|
||||
private static final DateTimeFormatter FOLLOW_UP_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
|
||||
private static final Logger log = LoggerFactory.getLogger(ExpansionServiceImpl.class);
|
||||
|
||||
private final ExpansionMapper expansionMapper;
|
||||
|
||||
public ExpansionServiceImpl(ExpansionMapper expansionMapper) {
|
||||
this.expansionMapper = expansionMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpansionMetaDTO getMeta() {
|
||||
try {
|
||||
return new ExpansionMetaDTO(expansionMapper.selectDepartments());
|
||||
} catch (Exception ex) {
|
||||
log.warn("Failed to load expansion departments, fallback to empty list", ex);
|
||||
return new ExpansionMetaDTO(Collections.emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpansionOverviewDTO getOverview(Long userId, String keyword) {
|
||||
String normalizedKeyword = normalizeKeyword(keyword);
|
||||
List<SalesExpansionItemDTO> salesItems = expansionMapper.selectSalesExpansions(userId, normalizedKeyword);
|
||||
List<ChannelExpansionItemDTO> channelItems = expansionMapper.selectChannelExpansions(userId, normalizedKeyword);
|
||||
|
||||
attachSalesFollowUps(userId, salesItems);
|
||||
attachChannelFollowUps(userId, channelItems);
|
||||
|
||||
return new ExpansionOverviewDTO(salesItems, channelItems);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long createSalesExpansion(Long userId, CreateSalesExpansionRequest request) {
|
||||
fillSalesDefaults(request);
|
||||
expansionMapper.insertSalesExpansion(userId, request);
|
||||
if (request.getId() == null) {
|
||||
throw new BusinessException("销售拓展新增失败");
|
||||
}
|
||||
return request.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long createChannelExpansion(Long userId, CreateChannelExpansionRequest request) {
|
||||
fillChannelDefaults(request);
|
||||
expansionMapper.insertChannelExpansion(userId, request);
|
||||
if (request.getId() == null) {
|
||||
throw new BusinessException("渠道拓展新增失败");
|
||||
}
|
||||
return request.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateSalesExpansion(Long userId, Long id, UpdateSalesExpansionRequest request) {
|
||||
fillSalesDefaults(request);
|
||||
int updated = expansionMapper.updateSalesExpansion(userId, id, request);
|
||||
if (updated <= 0) {
|
||||
throw new BusinessException("未找到可编辑的销售拓展记录");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateChannelExpansion(Long userId, Long id, UpdateChannelExpansionRequest request) {
|
||||
fillChannelDefaults(request);
|
||||
int updated = expansionMapper.updateChannelExpansion(userId, id, request);
|
||||
if (updated <= 0) {
|
||||
throw new BusinessException("未找到可编辑的渠道拓展记录");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long createFollowUp(Long userId, String bizType, Long bizId, CreateExpansionFollowUpRequest request) {
|
||||
String normalizedBizType = normalizeBizType(bizType);
|
||||
ensureOwnedExpansion(userId, normalizedBizType, bizId);
|
||||
int inserted = expansionMapper.insertExpansionFollowUp(normalizedBizType, bizId, userId, request);
|
||||
if (inserted <= 0) {
|
||||
throw new BusinessException("跟进记录新增失败");
|
||||
}
|
||||
return Long.valueOf(inserted);
|
||||
}
|
||||
|
||||
private void attachSalesFollowUps(Long userId, List<SalesExpansionItemDTO> salesItems) {
|
||||
List<Long> bizIds = salesItems.stream()
|
||||
.map(SalesExpansionItemDTO::getId)
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
if (bizIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<Long, List<ExpansionFollowUpDTO>> grouped = expansionMapper.selectSalesFollowUps(userId, bizIds).stream()
|
||||
.peek(this::fillFollowUpDisplayFields)
|
||||
.collect(Collectors.groupingBy(ExpansionFollowUpDTO::getBizId));
|
||||
|
||||
for (SalesExpansionItemDTO item : salesItems) {
|
||||
item.setFollowUps(grouped.getOrDefault(item.getId(), Collections.emptyList()));
|
||||
}
|
||||
}
|
||||
|
||||
private void attachChannelFollowUps(Long userId, List<ChannelExpansionItemDTO> channelItems) {
|
||||
List<Long> bizIds = channelItems.stream()
|
||||
.map(ChannelExpansionItemDTO::getId)
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
if (bizIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<Long, List<ExpansionFollowUpDTO>> grouped = expansionMapper.selectChannelFollowUps(userId, bizIds).stream()
|
||||
.peek(this::fillFollowUpDisplayFields)
|
||||
.collect(Collectors.groupingBy(ExpansionFollowUpDTO::getBizId));
|
||||
|
||||
for (ChannelExpansionItemDTO item : channelItems) {
|
||||
item.setFollowUps(grouped.getOrDefault(item.getId(), Collections.emptyList()));
|
||||
}
|
||||
}
|
||||
|
||||
private void fillFollowUpDisplayFields(ExpansionFollowUpDTO followUp) {
|
||||
if (followUp.getFollowUpTime() != null) {
|
||||
followUp.setDate(followUp.getFollowUpTime().format(FOLLOW_UP_TIME_FORMATTER));
|
||||
}
|
||||
if (isBlank(followUp.getType())) {
|
||||
followUp.setType("无");
|
||||
}
|
||||
if (isBlank(followUp.getContent())) {
|
||||
followUp.setContent("无");
|
||||
}
|
||||
if (isBlank(followUp.getUser())) {
|
||||
followUp.setUser("无");
|
||||
}
|
||||
}
|
||||
|
||||
private String normalizeKeyword(String keyword) {
|
||||
if (keyword == null) {
|
||||
return null;
|
||||
}
|
||||
String trimmed = keyword.trim();
|
||||
return trimmed.isEmpty() ? null : trimmed;
|
||||
}
|
||||
|
||||
private boolean isBlank(String value) {
|
||||
return value == null || value.trim().isEmpty();
|
||||
}
|
||||
|
||||
private void fillSalesDefaults(CreateSalesExpansionRequest request) {
|
||||
if (isBlank(request.getIntentLevel())) {
|
||||
request.setIntentLevel("medium");
|
||||
}
|
||||
if (isBlank(request.getStage())) {
|
||||
request.setStage("initial_contact");
|
||||
}
|
||||
if (request.getHasDesktopExp() == null) {
|
||||
request.setHasDesktopExp(Boolean.FALSE);
|
||||
}
|
||||
if (request.getInProgress() == null) {
|
||||
request.setInProgress(Boolean.TRUE);
|
||||
}
|
||||
if (isBlank(request.getEmploymentStatus())) {
|
||||
request.setEmploymentStatus("active");
|
||||
}
|
||||
}
|
||||
|
||||
private void fillSalesDefaults(UpdateSalesExpansionRequest request) {
|
||||
if (isBlank(request.getIntentLevel())) {
|
||||
request.setIntentLevel("medium");
|
||||
}
|
||||
if (isBlank(request.getStage())) {
|
||||
request.setStage("initial_contact");
|
||||
}
|
||||
if (request.getHasDesktopExp() == null) {
|
||||
request.setHasDesktopExp(Boolean.FALSE);
|
||||
}
|
||||
if (request.getInProgress() == null) {
|
||||
request.setInProgress(Boolean.TRUE);
|
||||
}
|
||||
if (isBlank(request.getEmploymentStatus())) {
|
||||
request.setEmploymentStatus("active");
|
||||
}
|
||||
}
|
||||
|
||||
private void fillChannelDefaults(CreateChannelExpansionRequest request) {
|
||||
if (isBlank(request.getStage())) {
|
||||
request.setStage("initial_contact");
|
||||
}
|
||||
if (request.getLandedFlag() == null) {
|
||||
request.setLandedFlag(Boolean.FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
private void fillChannelDefaults(UpdateChannelExpansionRequest request) {
|
||||
if (isBlank(request.getStage())) {
|
||||
request.setStage("initial_contact");
|
||||
}
|
||||
if (request.getLandedFlag() == null) {
|
||||
request.setLandedFlag(Boolean.FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
private String normalizeBizType(String bizType) {
|
||||
if ("sales".equalsIgnoreCase(bizType)) {
|
||||
return "sales";
|
||||
}
|
||||
if ("channel".equalsIgnoreCase(bizType)) {
|
||||
return "channel";
|
||||
}
|
||||
throw new BusinessException("不支持的拓展类型");
|
||||
}
|
||||
|
||||
private void ensureOwnedExpansion(Long userId, String bizType, Long bizId) {
|
||||
if (bizId == null || bizId <= 0) {
|
||||
throw new BusinessException("拓展记录不存在");
|
||||
}
|
||||
|
||||
int count = "sales".equals(bizType)
|
||||
? expansionMapper.countOwnedSalesExpansion(userId, bizId)
|
||||
: expansionMapper.countOwnedChannelExpansion(userId, bizId);
|
||||
if (count <= 0) {
|
||||
throw new BusinessException("无权操作该拓展记录");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
package com.unis.crm.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||
import com.unis.crm.common.BusinessException;
|
||||
import com.unis.crm.dto.opportunity.CreateOpportunityFollowUpRequest;
|
||||
import com.unis.crm.dto.opportunity.CreateOpportunityRequest;
|
||||
import com.unis.crm.dto.opportunity.OpportunityFollowUpDTO;
|
||||
import com.unis.crm.dto.opportunity.OpportunityItemDTO;
|
||||
import com.unis.crm.dto.opportunity.OpportunityOverviewDTO;
|
||||
import com.unis.crm.mapper.OpportunityMapper;
|
||||
import com.unis.crm.service.OpportunityService;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class OpportunityServiceImpl implements OpportunityService {
|
||||
|
||||
private final OpportunityMapper opportunityMapper;
|
||||
|
||||
public OpportunityServiceImpl(OpportunityMapper opportunityMapper) {
|
||||
this.opportunityMapper = opportunityMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpportunityOverviewDTO getOverview(Long userId, String keyword, String stage) {
|
||||
String normalizedKeyword = normalizeKeyword(keyword);
|
||||
String normalizedStage = normalizeStage(stage);
|
||||
List<OpportunityItemDTO> items = opportunityMapper.selectOpportunities(userId, normalizedKeyword, normalizedStage);
|
||||
attachFollowUps(userId, items);
|
||||
return new OpportunityOverviewDTO(items);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long createOpportunity(Long userId, CreateOpportunityRequest request) {
|
||||
fillDefaults(request);
|
||||
Long customerId = opportunityMapper.selectOwnedCustomerIdByName(userId, request.getCustomerName().trim());
|
||||
if (customerId == null) {
|
||||
customerId = IdWorker.getId();
|
||||
opportunityMapper.insertCustomer(customerId, userId, request.getCustomerName().trim(), request.getSource());
|
||||
}
|
||||
|
||||
opportunityMapper.insertOpportunity(userId, customerId, request);
|
||||
if (request.getId() == null) {
|
||||
throw new BusinessException("商机新增失败");
|
||||
}
|
||||
return request.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long updateOpportunity(Long userId, Long opportunityId, CreateOpportunityRequest request) {
|
||||
if (opportunityId == null || opportunityId <= 0) {
|
||||
throw new BusinessException("商机不存在");
|
||||
}
|
||||
if (opportunityMapper.countOwnedOpportunity(userId, opportunityId) <= 0) {
|
||||
throw new BusinessException("无权编辑该商机");
|
||||
}
|
||||
|
||||
fillDefaults(request);
|
||||
Long customerId = opportunityMapper.selectOwnedCustomerIdByName(userId, request.getCustomerName().trim());
|
||||
if (customerId == null) {
|
||||
customerId = IdWorker.getId();
|
||||
opportunityMapper.insertCustomer(customerId, userId, request.getCustomerName().trim(), request.getSource());
|
||||
}
|
||||
|
||||
int updated = opportunityMapper.updateOpportunity(userId, opportunityId, customerId, request);
|
||||
if (updated <= 0) {
|
||||
throw new BusinessException("商机更新失败");
|
||||
}
|
||||
return opportunityId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long createFollowUp(Long userId, Long opportunityId, CreateOpportunityFollowUpRequest request) {
|
||||
if (opportunityId == null || opportunityId <= 0) {
|
||||
throw new BusinessException("商机不存在");
|
||||
}
|
||||
if (opportunityMapper.countOwnedOpportunity(userId, opportunityId) <= 0) {
|
||||
throw new BusinessException("无权操作该商机");
|
||||
}
|
||||
int inserted = opportunityMapper.insertOpportunityFollowUp(userId, opportunityId, request);
|
||||
if (inserted <= 0) {
|
||||
throw new BusinessException("商机跟进新增失败");
|
||||
}
|
||||
return Long.valueOf(inserted);
|
||||
}
|
||||
|
||||
private void attachFollowUps(Long userId, List<OpportunityItemDTO> items) {
|
||||
List<Long> opportunityIds = items.stream()
|
||||
.map(OpportunityItemDTO::getId)
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
if (opportunityIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<Long, List<OpportunityFollowUpDTO>> grouped = opportunityMapper
|
||||
.selectOpportunityFollowUps(userId, opportunityIds)
|
||||
.stream()
|
||||
.peek(this::fillFollowUpDisplayFields)
|
||||
.collect(Collectors.groupingBy(OpportunityFollowUpDTO::getOpportunityId));
|
||||
|
||||
for (OpportunityItemDTO item : items) {
|
||||
item.setFollowUps(grouped.getOrDefault(item.getId(), Collections.emptyList()));
|
||||
}
|
||||
}
|
||||
|
||||
private void fillFollowUpDisplayFields(OpportunityFollowUpDTO followUp) {
|
||||
if (isBlank(followUp.getType())) {
|
||||
followUp.setType("无");
|
||||
}
|
||||
if (isBlank(followUp.getContent())) {
|
||||
followUp.setContent("无");
|
||||
}
|
||||
if (isBlank(followUp.getUser())) {
|
||||
followUp.setUser("无");
|
||||
}
|
||||
if (!isBlank(followUp.getDate())) {
|
||||
followUp.setDate(followUp.getDate());
|
||||
}
|
||||
}
|
||||
|
||||
private void fillDefaults(CreateOpportunityRequest request) {
|
||||
request.setCustomerName(request.getCustomerName().trim());
|
||||
request.setOpportunityName(request.getOpportunityName().trim());
|
||||
if (request.getExpectedCloseDate() == null) {
|
||||
throw new BusinessException("预计结单日期不能为空");
|
||||
}
|
||||
if (isBlank(request.getStage())) {
|
||||
request.setStage("initial_contact");
|
||||
} else {
|
||||
request.setStage(toStageCode(request.getStage()));
|
||||
}
|
||||
if (isBlank(request.getOpportunityType())) {
|
||||
request.setOpportunityType("新建");
|
||||
}
|
||||
if (isBlank(request.getProductType())) {
|
||||
request.setProductType("VDI云桌面");
|
||||
}
|
||||
if (isBlank(request.getSource())) {
|
||||
request.setSource("主动开发");
|
||||
}
|
||||
if (request.getPushedToOms() == null) {
|
||||
request.setPushedToOms(Boolean.FALSE);
|
||||
}
|
||||
if (request.getConfidencePct() == null) {
|
||||
request.setConfidencePct(50);
|
||||
}
|
||||
}
|
||||
|
||||
private String normalizeKeyword(String keyword) {
|
||||
if (keyword == null) {
|
||||
return null;
|
||||
}
|
||||
String trimmed = keyword.trim();
|
||||
return trimmed.isEmpty() ? null : trimmed;
|
||||
}
|
||||
|
||||
private String normalizeStage(String stage) {
|
||||
if (stage == null) {
|
||||
return null;
|
||||
}
|
||||
String trimmed = stage.trim();
|
||||
if (trimmed.isEmpty() || "全部".equals(trimmed)) {
|
||||
return null;
|
||||
}
|
||||
return toStageCode(trimmed);
|
||||
}
|
||||
|
||||
private boolean isBlank(String value) {
|
||||
return value == null || value.trim().isEmpty();
|
||||
}
|
||||
|
||||
private String toStageCode(String value) {
|
||||
return switch (value) {
|
||||
case "初步沟通", "initial_contact" -> "initial_contact";
|
||||
case "方案交流", "solution_discussion" -> "solution_discussion";
|
||||
case "招投标", "bidding" -> "bidding";
|
||||
case "商务谈判", "business_negotiation" -> "business_negotiation";
|
||||
case "已成交", "won" -> "won";
|
||||
case "已放弃", "lost" -> "lost";
|
||||
default -> throw new BusinessException("不支持的商机阶段");
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,333 @@
|
|||
package com.unis.crm.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||
import com.unis.crm.common.BusinessException;
|
||||
import com.unis.crm.dto.work.CreateWorkCheckInRequest;
|
||||
import com.unis.crm.dto.work.CreateWorkDailyReportRequest;
|
||||
import com.unis.crm.dto.work.WorkCheckInDTO;
|
||||
import com.unis.crm.dto.work.WorkDailyReportDTO;
|
||||
import com.unis.crm.dto.work.WorkHistoryItemDTO;
|
||||
import com.unis.crm.dto.work.WorkOverviewDTO;
|
||||
import com.unis.crm.mapper.WorkMapper;
|
||||
import com.unis.crm.service.WorkService;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class WorkServiceImpl implements WorkService {
|
||||
|
||||
private static final String NOMINATIM_BASE_URL = "https://nominatim.openstreetmap.org/reverse";
|
||||
|
||||
private final WorkMapper workMapper;
|
||||
private final HttpClient httpClient;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public WorkServiceImpl(WorkMapper workMapper, ObjectMapper objectMapper) {
|
||||
this.workMapper = workMapper;
|
||||
this.objectMapper = objectMapper;
|
||||
this.httpClient = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(8))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorkOverviewDTO getOverview(Long userId) {
|
||||
requireUser(userId);
|
||||
WorkCheckInDTO todayCheckIn = workMapper.selectTodayCheckIn(userId);
|
||||
enrichCheckInLocation(todayCheckIn);
|
||||
WorkDailyReportDTO todayReport = workMapper.selectTodayReport(userId);
|
||||
String suggestedWorkContent = buildSuggestedWorkContent(workMapper.selectTodayWorkContentLines(userId));
|
||||
List<WorkHistoryItemDTO> history = workMapper.selectHistory(userId);
|
||||
return new WorkOverviewDTO(todayCheckIn, todayReport, suggestedWorkContent, history);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long saveCheckIn(Long userId, CreateWorkCheckInRequest request) {
|
||||
requireUser(userId);
|
||||
request.setLocationText(request.getLocationText().trim());
|
||||
request.setRemark(normalizeOptionalText(request.getRemark()));
|
||||
|
||||
int affectedRows = workMapper.insertCheckIn(userId, request);
|
||||
Long checkInId = workMapper.selectTodayCheckInId(userId);
|
||||
|
||||
if (affectedRows <= 0 || checkInId == null) {
|
||||
throw new BusinessException("外勤打卡保存失败");
|
||||
}
|
||||
return checkInId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long saveDailyReport(Long userId, CreateWorkDailyReportRequest request) {
|
||||
requireUser(userId);
|
||||
request.setWorkContent(request.getWorkContent().trim());
|
||||
request.setTomorrowPlan(request.getTomorrowPlan().trim());
|
||||
request.setSourceType(normalizeOptionalText(request.getSourceType()));
|
||||
if (request.getSourceType() == null) {
|
||||
request.setSourceType("manual");
|
||||
}
|
||||
|
||||
Long reportId = workMapper.selectTodayReportId(userId);
|
||||
int affectedRows;
|
||||
if (reportId == null) {
|
||||
affectedRows = workMapper.insertDailyReport(userId, request);
|
||||
reportId = workMapper.selectTodayReportId(userId);
|
||||
} else {
|
||||
affectedRows = workMapper.updateDailyReport(reportId, request);
|
||||
}
|
||||
|
||||
if (affectedRows <= 0 || reportId == null) {
|
||||
throw new BusinessException("日报保存失败");
|
||||
}
|
||||
syncTomorrowPlanTodo(userId, reportId, request.getTomorrowPlan());
|
||||
return reportId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resolveLocationName(BigDecimal latitude, BigDecimal longitude) {
|
||||
if (latitude == null || longitude == null) {
|
||||
throw new BusinessException("定位坐标不能为空");
|
||||
}
|
||||
|
||||
try {
|
||||
String requestUrl = NOMINATIM_BASE_URL
|
||||
+ "?format=jsonv2&addressdetails=1&namedetails=1&extratags=1&zoom=18"
|
||||
+ "&lat=" + URLEncoder.encode(latitude.stripTrailingZeros().toPlainString(), StandardCharsets.UTF_8)
|
||||
+ "&lon=" + URLEncoder.encode(longitude.stripTrailingZeros().toPlainString(), StandardCharsets.UTF_8)
|
||||
+ "&accept-language=" + URLEncoder.encode("zh-CN,zh;q=0.9,en;q=0.8", StandardCharsets.UTF_8);
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(requestUrl))
|
||||
.header("Accept", "application/json")
|
||||
.header("User-Agent", "unis-crm-backend/1.0 (workbench reverse geocoding)")
|
||||
.timeout(Duration.ofSeconds(10))
|
||||
.GET()
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
||||
if (response.statusCode() < 200 || response.statusCode() >= 300) {
|
||||
throw new BusinessException("地点解析失败,请稍后重试");
|
||||
}
|
||||
|
||||
JsonNode root = objectMapper.readTree(response.body());
|
||||
JsonNode addressNode = root.path("address");
|
||||
String orderedLocation = buildOrderedLocationName(addressNode);
|
||||
if (orderedLocation != null) {
|
||||
return orderedLocation;
|
||||
}
|
||||
|
||||
String displayName = textValue(root, "display_name");
|
||||
if (displayName != null) {
|
||||
String normalizedDisplayName = normalizeDisplayName(displayName);
|
||||
if (normalizedDisplayName != null) {
|
||||
return normalizedDisplayName;
|
||||
}
|
||||
return displayName;
|
||||
}
|
||||
} catch (BusinessException exception) {
|
||||
throw exception;
|
||||
} catch (Exception exception) {
|
||||
throw new BusinessException("地点解析失败,请检查网络后重试");
|
||||
}
|
||||
|
||||
throw new BusinessException("未能解析出具体地点名称");
|
||||
}
|
||||
|
||||
private void requireUser(Long userId) {
|
||||
if (userId == null || userId <= 0) {
|
||||
throw new BusinessException("未获取到当前登录用户");
|
||||
}
|
||||
}
|
||||
|
||||
private String buildSuggestedWorkContent(List<String> lines) {
|
||||
if (lines == null || lines.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
int index = 1;
|
||||
for (String line : lines) {
|
||||
String normalized = normalizeOptionalText(line);
|
||||
if (normalized == null) {
|
||||
continue;
|
||||
}
|
||||
if (builder.length() > 0) {
|
||||
builder.append('\n');
|
||||
}
|
||||
builder.append(index).append(". ").append(normalized);
|
||||
index++;
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private String normalizeOptionalText(String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
String trimmed = value.trim();
|
||||
return trimmed.isEmpty() ? null : trimmed;
|
||||
}
|
||||
|
||||
private String textValue(JsonNode node, String fieldName) {
|
||||
if (node == null || node.isMissingNode()) {
|
||||
return null;
|
||||
}
|
||||
String value = normalizeOptionalText(node.path(fieldName).asText(null));
|
||||
return value;
|
||||
}
|
||||
|
||||
private String joinNonBlank(String... values) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (String value : values) {
|
||||
String normalized = normalizeOptionalText(value);
|
||||
if (normalized == null) {
|
||||
continue;
|
||||
}
|
||||
if (builder.indexOf(normalized) >= 0) {
|
||||
continue;
|
||||
}
|
||||
builder.append(normalized);
|
||||
}
|
||||
return builder.length() == 0 ? null : builder.toString();
|
||||
}
|
||||
|
||||
private String buildOrderedLocationName(JsonNode addressNode) {
|
||||
if (addressNode == null || addressNode.isMissingNode()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return joinNonBlank(
|
||||
textValue(addressNode, "state"),
|
||||
firstNonBlank(
|
||||
textValue(addressNode, "province"),
|
||||
textValue(addressNode, "region"),
|
||||
textValue(addressNode, "locality")),
|
||||
firstNonBlank(
|
||||
textValue(addressNode, "city"),
|
||||
textValue(addressNode, "municipality"),
|
||||
textValue(addressNode, "town"),
|
||||
textValue(addressNode, "county")),
|
||||
firstNonBlank(
|
||||
textValue(addressNode, "district"),
|
||||
textValue(addressNode, "city_district"),
|
||||
textValue(addressNode, "borough")),
|
||||
firstNonBlank(
|
||||
textValue(addressNode, "suburb"),
|
||||
textValue(addressNode, "township"),
|
||||
textValue(addressNode, "residential"),
|
||||
textValue(addressNode, "commercial"),
|
||||
textValue(addressNode, "industrial"),
|
||||
textValue(addressNode, "retail"),
|
||||
textValue(addressNode, "quarter"),
|
||||
textValue(addressNode, "neighbourhood"),
|
||||
textValue(addressNode, "village"),
|
||||
textValue(addressNode, "hamlet")),
|
||||
firstNonBlank(
|
||||
textValue(addressNode, "city_block"),
|
||||
textValue(addressNode, "residential"),
|
||||
textValue(addressNode, "commercial"),
|
||||
textValue(addressNode, "allotments")),
|
||||
firstNonBlank(
|
||||
textValue(addressNode, "road"),
|
||||
textValue(addressNode, "street"),
|
||||
textValue(addressNode, "pedestrian")),
|
||||
joinNonBlank(
|
||||
textValue(addressNode, "house_number"),
|
||||
textValue(addressNode, "house_name")),
|
||||
firstNonBlank(
|
||||
textValue(addressNode, "building"),
|
||||
textValue(addressNode, "amenity"),
|
||||
textValue(addressNode, "office"),
|
||||
textValue(addressNode, "shop"))
|
||||
);
|
||||
}
|
||||
|
||||
private String normalizeDisplayName(String displayName) {
|
||||
String normalized = normalizeOptionalText(displayName);
|
||||
if (normalized == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String[] segments = normalized.split("\\s*,\\s*");
|
||||
if (segments.length == 0) {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (int index = segments.length - 1; index >= 0; index--) {
|
||||
String segment = normalizeOptionalText(segments[index]);
|
||||
if (segment == null
|
||||
|| "中国".equals(segment)
|
||||
|| segment.matches("\\d{6}")
|
||||
|| builder.indexOf(segment) >= 0) {
|
||||
continue;
|
||||
}
|
||||
builder.append(segment);
|
||||
}
|
||||
return builder.length() == 0 ? normalized : builder.toString();
|
||||
}
|
||||
|
||||
private void enrichCheckInLocation(WorkCheckInDTO todayCheckIn) {
|
||||
if (todayCheckIn == null || todayCheckIn.getLatitude() == null || todayCheckIn.getLongitude() == null) {
|
||||
return;
|
||||
}
|
||||
if (!shouldRefreshLocationText(todayCheckIn.getLocationText())) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
todayCheckIn.setLocationText(resolveLocationName(todayCheckIn.getLatitude(), todayCheckIn.getLongitude()));
|
||||
} catch (Exception ignored) {
|
||||
// Keep the stored location text when reverse geocoding is unavailable.
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldRefreshLocationText(String locationText) {
|
||||
String normalized = normalizeOptionalText(locationText);
|
||||
if (normalized == null) {
|
||||
return true;
|
||||
}
|
||||
return normalized.contains(",")
|
||||
|| normalized.contains(",")
|
||||
|| normalized.length() < 14;
|
||||
}
|
||||
|
||||
private void syncTomorrowPlanTodo(Long userId, Long reportId, String tomorrowPlan) {
|
||||
String todoTitle = buildTomorrowPlanTodoTitle(tomorrowPlan);
|
||||
Long todoId = workMapper.selectTodoIdByBiz(userId, "report", reportId);
|
||||
if (todoId == null) {
|
||||
workMapper.insertTodo(IdWorker.getId(), userId, todoTitle, "report", reportId);
|
||||
return;
|
||||
}
|
||||
workMapper.updateTodo(todoId, todoTitle);
|
||||
}
|
||||
|
||||
private String buildTomorrowPlanTodoTitle(String tomorrowPlan) {
|
||||
String normalized = normalizeOptionalText(tomorrowPlan);
|
||||
if (normalized == null) {
|
||||
return "明日工作计划";
|
||||
}
|
||||
String singleLine = normalized.replace("\r", "\n").replace("\n", " ");
|
||||
String title = "明日计划:" + singleLine;
|
||||
return title.length() > 200 ? title.substring(0, 200) : title;
|
||||
}
|
||||
|
||||
private String firstNonBlank(String... values) {
|
||||
for (String value : values) {
|
||||
String normalized = normalizeOptionalText(value);
|
||||
if (normalized != null) {
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
server:
|
||||
port: 8080
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: unis-crm-backend
|
||||
datasource:
|
||||
url: jdbc:postgresql://127.0.0.1:5432/nex_auth
|
||||
username: postgres
|
||||
password: 199628
|
||||
driver-class-name: org.postgresql.Driver
|
||||
data:
|
||||
redis:
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
password: 199628@tlw
|
||||
database: 14
|
||||
|
||||
mybatis-plus:
|
||||
mapper-locations: classpath*:/mapper/**/*.xml
|
||||
type-aliases-package: com.unis.crm.dto.dashboard
|
||||
configuration:
|
||||
map-underscore-to-camel-case: true
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
|
||||
logging:
|
||||
level:
|
||||
com.unis.crm: info
|
||||
|
||||
unisbase:
|
||||
tenant:
|
||||
enabled: false
|
||||
web:
|
||||
auth-endpoints-enabled: true
|
||||
management-endpoints-enabled: true
|
||||
security:
|
||||
enabled: true
|
||||
mode: embedded
|
||||
jwt-secret: change-me-please-change-me-32bytes
|
||||
auth-header: Authorization
|
||||
token-prefix: "Bearer "
|
||||
permit-all-urls:
|
||||
- /actuator/health
|
||||
internal-auth:
|
||||
enabled: true
|
||||
secret: change-me-internal-secret
|
||||
header-name: X-Internal-Secret
|
||||
app:
|
||||
upload-path: /Users/kangwenjing/Downloads/crm/uploads
|
||||
resource-prefix: /sys/api/static/
|
||||
captcha:
|
||||
ttl-seconds: 120
|
||||
max-attempts: 5
|
||||
token:
|
||||
access-default-minutes: 30
|
||||
refresh-default-days: 7
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.unis.crm.mapper.DashboardMapper">
|
||||
|
||||
<select id="selectDefaultUserId" resultType="java.lang.Long">
|
||||
select user_id
|
||||
from sys_user
|
||||
where status = 1
|
||||
order by user_id asc
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<select id="selectUserWelcome" resultType="com.unis.crm.dto.dashboard.UserWelcomeDTO">
|
||||
select
|
||||
u.user_id as userId,
|
||||
u.display_name as realName,
|
||||
null as jobTitle,
|
||||
null as deptName,
|
||||
null as hireDate
|
||||
from sys_user u
|
||||
where u.user_id = #{userId}
|
||||
and u.status = 1
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<select id="selectDashboardStats" resultType="com.unis.crm.dto.dashboard.DashboardStatDTO">
|
||||
select '本月新增商机' as name,
|
||||
count(1)::bigint as value,
|
||||
'monthlyOpportunities' as metricKey
|
||||
from crm_opportunity
|
||||
where owner_user_id = #{userId}
|
||||
and date_trunc('month', created_at) = date_trunc('month', now())
|
||||
|
||||
union all
|
||||
|
||||
select '跟进中客户' as name,
|
||||
count(1)::bigint as value,
|
||||
'followingCustomers' as metricKey
|
||||
from crm_customer
|
||||
where owner_user_id = #{userId}
|
||||
and status = 'following'
|
||||
|
||||
union all
|
||||
|
||||
select '已成单项目' as name,
|
||||
count(1)::bigint as value,
|
||||
'wonProjects' as metricKey
|
||||
from crm_opportunity
|
||||
where owner_user_id = #{userId}
|
||||
and stage = 'won'
|
||||
|
||||
union all
|
||||
|
||||
select '本月打卡天数' as name,
|
||||
count(distinct checkin_date)::bigint as value,
|
||||
'monthlyCheckins' as metricKey
|
||||
from work_checkin
|
||||
where user_id = #{userId}
|
||||
and date_trunc('month', checkin_date::timestamp) = date_trunc('month', now())
|
||||
</select>
|
||||
|
||||
<select id="selectPendingTodos" resultType="com.unis.crm.dto.dashboard.DashboardTodoDTO">
|
||||
select
|
||||
id,
|
||||
title,
|
||||
biz_type as bizType,
|
||||
biz_id as bizId,
|
||||
priority,
|
||||
status,
|
||||
due_date as dueDate,
|
||||
created_at as createdAt
|
||||
from work_todo
|
||||
where user_id = #{userId}
|
||||
and status = 'todo'
|
||||
order by
|
||||
case priority
|
||||
when 'high' then 1
|
||||
when 'medium' then 2
|
||||
else 3
|
||||
end,
|
||||
coalesce(due_date, created_at) asc
|
||||
limit 6
|
||||
</select>
|
||||
|
||||
<select id="selectLatestActivities" resultType="com.unis.crm.dto.dashboard.DashboardActivityDTO">
|
||||
with latest_report_comment as (
|
||||
select distinct on (c.report_id)
|
||||
c.report_id,
|
||||
c.reviewer_user_id,
|
||||
c.score,
|
||||
c.comment_content,
|
||||
c.reviewed_at
|
||||
from work_daily_report_comment c
|
||||
order by c.report_id, c.reviewed_at desc, c.id desc
|
||||
),
|
||||
activity_union as (
|
||||
select
|
||||
l.id,
|
||||
l.biz_type as bizType,
|
||||
l.biz_id as bizId,
|
||||
l.action_type as actionType,
|
||||
l.title,
|
||||
l.content,
|
||||
l.operator_user_id as operatorUserId,
|
||||
l.created_at as createdAt
|
||||
from sys_activity_log l
|
||||
where l.operator_user_id = #{userId}
|
||||
or l.operator_user_id is null
|
||||
|
||||
union all
|
||||
|
||||
select
|
||||
(1000000000 + o.id) as id,
|
||||
'opportunity' as bizType,
|
||||
o.id as bizId,
|
||||
'stage_update' as actionType,
|
||||
'商机阶段更新' as title,
|
||||
o.opportunity_name || ' 已推进至' ||
|
||||
case o.stage
|
||||
when 'initial_contact' then '初步沟通'
|
||||
when 'solution_discussion' then '方案交流'
|
||||
when 'bidding' then '招投标'
|
||||
when 'business_negotiation' then '商务谈判'
|
||||
when 'won' then '已成交'
|
||||
when 'lost' then '已输单'
|
||||
else o.stage
|
||||
end || '阶段' as content,
|
||||
o.owner_user_id as operatorUserId,
|
||||
o.updated_at as createdAt
|
||||
from crm_opportunity o
|
||||
where o.owner_user_id = #{userId}
|
||||
and o.updated_at > o.created_at
|
||||
|
||||
union all
|
||||
|
||||
select
|
||||
(2000000000 + r.id) as id,
|
||||
'report' as bizType,
|
||||
r.id as bizId,
|
||||
case
|
||||
when r.status = 'reviewed' or lc.score is not null then 'report_reviewed'
|
||||
else 'report_read'
|
||||
end as actionType,
|
||||
case
|
||||
when r.status = 'reviewed' or lc.score is not null then '日报已点评'
|
||||
else '日报已阅'
|
||||
end as title,
|
||||
case
|
||||
when lc.score is not null then '主管对你' || to_char(r.report_date, 'MM-DD') || '的日报给出了 ' || lc.score || ' 分'
|
||||
when r.status = 'reviewed' then '你的' || to_char(r.report_date, 'MM-DD') || '日报已完成主管点评'
|
||||
else '你的' || to_char(r.report_date, 'MM-DD') || '日报已被查阅'
|
||||
end as content,
|
||||
coalesce(lc.reviewer_user_id, r.user_id) as operatorUserId,
|
||||
coalesce(lc.reviewed_at, r.updated_at, r.created_at) as createdAt
|
||||
from work_daily_report r
|
||||
left join latest_report_comment lc on lc.report_id = r.id
|
||||
where r.user_id = #{userId}
|
||||
and r.status in ('read', 'reviewed')
|
||||
|
||||
union all
|
||||
|
||||
select
|
||||
(3000000000 + c.id) as id,
|
||||
'channel' as bizType,
|
||||
c.id as bizId,
|
||||
'channel_created' as actionType,
|
||||
'新渠道录入' as title,
|
||||
'成功录入 ' || c.channel_name || ' 渠道商信息' as content,
|
||||
c.owner_user_id as operatorUserId,
|
||||
c.created_at as createdAt
|
||||
from crm_channel_expansion c
|
||||
where c.owner_user_id = #{userId}
|
||||
|
||||
union all
|
||||
|
||||
select
|
||||
(4000000000 + f.id) as id,
|
||||
'opportunity_followup' as bizType,
|
||||
f.opportunity_id as bizId,
|
||||
'opportunity_followup' as actionType,
|
||||
'商机跟进新增' as title,
|
||||
o.opportunity_name || ' 新增了一条' || f.followup_type || '跟进记录' as content,
|
||||
f.followup_user_id as operatorUserId,
|
||||
f.followup_time as createdAt
|
||||
from crm_opportunity_followup f
|
||||
join crm_opportunity o on o.id = f.opportunity_id
|
||||
where f.followup_user_id = #{userId}
|
||||
)
|
||||
select
|
||||
a.id,
|
||||
a.bizType,
|
||||
a.bizId,
|
||||
a.actionType,
|
||||
a.title,
|
||||
a.content,
|
||||
a.operatorUserId,
|
||||
u.display_name as operatorName,
|
||||
a.createdAt
|
||||
from activity_union a
|
||||
left join sys_user u on u.user_id = a.operatorUserId
|
||||
order by a.createdAt desc nulls last
|
||||
limit 8
|
||||
</select>
|
||||
</mapper>
|
||||
|
|
@ -0,0 +1,279 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.unis.crm.mapper.ExpansionMapper">
|
||||
|
||||
<select id="selectDepartments" resultType="com.unis.crm.dto.expansion.DepartmentOptionDTO">
|
||||
select
|
||||
id,
|
||||
org_name as name
|
||||
from sys_org
|
||||
where status = 1
|
||||
order by id asc
|
||||
</select>
|
||||
|
||||
<select id="selectSalesExpansions" resultType="com.unis.crm.dto.expansion.SalesExpansionItemDTO">
|
||||
select
|
||||
s.id,
|
||||
'sales' as type,
|
||||
s.candidate_name as name,
|
||||
coalesce(s.mobile, '无') as phone,
|
||||
coalesce(s.email, '无') as email,
|
||||
s.target_dept_id as targetDeptId,
|
||||
'无' as dept,
|
||||
coalesce(s.industry, '无') as industry,
|
||||
coalesce(s.title, '无') as title,
|
||||
s.intent_level as intentLevel,
|
||||
case s.intent_level
|
||||
when 'high' then '高'
|
||||
when 'medium' then '中'
|
||||
when 'low' then '低'
|
||||
else '无'
|
||||
end as intent,
|
||||
s.stage as stageCode,
|
||||
case s.stage
|
||||
when 'initial_contact' then '初步沟通'
|
||||
when 'solution_discussion' then '方案交流'
|
||||
when 'bidding' then '招投标'
|
||||
when 'business_negotiation' then '商务谈判'
|
||||
when 'won' then '已成交'
|
||||
when 'lost' then '已放弃'
|
||||
else coalesce(s.stage, '无')
|
||||
end as stage,
|
||||
s.has_desktop_exp as hasExp,
|
||||
s.in_progress as inProgress,
|
||||
(s.employment_status = 'active') as active,
|
||||
s.employment_status as employmentStatus,
|
||||
coalesce(to_char(s.expected_join_date, 'YYYY-MM-DD'), '无') as expectedJoinDate,
|
||||
coalesce(s.remark, '无') as notes
|
||||
from crm_sales_expansion s
|
||||
where s.owner_user_id = #{userId}
|
||||
<if test="keyword != null and keyword != ''">
|
||||
and (
|
||||
s.candidate_name ilike concat('%', #{keyword}, '%')
|
||||
or coalesce(s.industry, '') ilike concat('%', #{keyword}, '%')
|
||||
)
|
||||
</if>
|
||||
order by s.updated_at desc, s.id desc
|
||||
</select>
|
||||
|
||||
<select id="selectChannelExpansions" resultType="com.unis.crm.dto.expansion.ChannelExpansionItemDTO">
|
||||
select
|
||||
c.id,
|
||||
'channel' as type,
|
||||
c.channel_name as name,
|
||||
coalesce(c.province, '无') as province,
|
||||
coalesce(c.industry, '无') as industry,
|
||||
coalesce(cast(c.annual_revenue as varchar), '') as annualRevenue,
|
||||
case
|
||||
when c.annual_revenue is null then '无'
|
||||
when c.annual_revenue >= 10000 then trim(to_char(c.annual_revenue / 10000.0, 'FM999999990.##')) || '万'
|
||||
else trim(to_char(c.annual_revenue, 'FM999999990.##'))
|
||||
end as revenue,
|
||||
coalesce(c.staff_size, 0) as size,
|
||||
coalesce(c.contact_name, '无') as contact,
|
||||
coalesce(c.contact_title, '无') as contactTitle,
|
||||
coalesce(c.contact_mobile, '无') as phone,
|
||||
c.stage as stageCode,
|
||||
case c.stage
|
||||
when 'initial_contact' then '初步接触'
|
||||
when 'solution_discussion' then '方案交流'
|
||||
when 'bidding' then '招投标'
|
||||
when 'business_negotiation' then '合作洽谈'
|
||||
when 'won' then '已合作'
|
||||
when 'lost' then '已终止'
|
||||
else coalesce(c.stage, '无')
|
||||
end as stage,
|
||||
c.landed_flag as landed,
|
||||
coalesce(to_char(c.expected_sign_date, 'YYYY-MM-DD'), '无') as expectedSignDate,
|
||||
coalesce(c.remark, '无') as notes
|
||||
from crm_channel_expansion c
|
||||
where c.owner_user_id = #{userId}
|
||||
<if test="keyword != null and keyword != ''">
|
||||
and (
|
||||
c.channel_name ilike concat('%', #{keyword}, '%')
|
||||
or coalesce(c.industry, '') ilike concat('%', #{keyword}, '%')
|
||||
or coalesce(c.province, '') ilike concat('%', #{keyword}, '%')
|
||||
)
|
||||
</if>
|
||||
order by c.updated_at desc, c.id desc
|
||||
</select>
|
||||
|
||||
<select id="selectSalesFollowUps" resultType="com.unis.crm.dto.expansion.ExpansionFollowUpDTO">
|
||||
select
|
||||
f.id,
|
||||
f.biz_id as bizId,
|
||||
f.biz_type as bizType,
|
||||
f.followup_time as followUpTime,
|
||||
f.followup_type as type,
|
||||
coalesce(f.content, '无') as content,
|
||||
coalesce(u.display_name, '无') as user
|
||||
from crm_expansion_followup f
|
||||
join crm_sales_expansion s on s.id = f.biz_id and f.biz_type = 'sales'
|
||||
left join sys_user u on u.user_id = f.followup_user_id
|
||||
where s.owner_user_id = #{userId}
|
||||
and f.biz_id in
|
||||
<foreach collection="bizIds" item="id" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
order by f.followup_time desc, f.id desc
|
||||
</select>
|
||||
|
||||
<select id="selectChannelFollowUps" resultType="com.unis.crm.dto.expansion.ExpansionFollowUpDTO">
|
||||
select
|
||||
f.id,
|
||||
f.biz_id as bizId,
|
||||
f.biz_type as bizType,
|
||||
f.followup_time as followUpTime,
|
||||
f.followup_type as type,
|
||||
coalesce(f.content, '无') as content,
|
||||
coalesce(u.display_name, '无') as user
|
||||
from crm_expansion_followup f
|
||||
join crm_channel_expansion c on c.id = f.biz_id and f.biz_type = 'channel'
|
||||
left join sys_user u on u.user_id = f.followup_user_id
|
||||
where c.owner_user_id = #{userId}
|
||||
and f.biz_id in
|
||||
<foreach collection="bizIds" item="id" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
order by f.followup_time desc, f.id desc
|
||||
</select>
|
||||
|
||||
<insert id="insertSalesExpansion" useGeneratedKeys="true" keyProperty="request.id">
|
||||
insert into crm_sales_expansion (
|
||||
candidate_name,
|
||||
mobile,
|
||||
email,
|
||||
target_dept_id,
|
||||
industry,
|
||||
title,
|
||||
intent_level,
|
||||
stage,
|
||||
has_desktop_exp,
|
||||
in_progress,
|
||||
employment_status,
|
||||
expected_join_date,
|
||||
owner_user_id,
|
||||
remark
|
||||
) values (
|
||||
#{request.candidateName},
|
||||
#{request.mobile},
|
||||
#{request.email},
|
||||
#{request.targetDeptId},
|
||||
#{request.industry},
|
||||
#{request.title},
|
||||
#{request.intentLevel},
|
||||
#{request.stage},
|
||||
#{request.hasDesktopExp},
|
||||
#{request.inProgress},
|
||||
#{request.employmentStatus},
|
||||
#{request.expectedJoinDate},
|
||||
#{userId},
|
||||
#{request.remark}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<insert id="insertChannelExpansion" useGeneratedKeys="true" keyProperty="request.id">
|
||||
insert into crm_channel_expansion (
|
||||
channel_name,
|
||||
province,
|
||||
industry,
|
||||
annual_revenue,
|
||||
staff_size,
|
||||
contact_name,
|
||||
contact_title,
|
||||
contact_mobile,
|
||||
stage,
|
||||
landed_flag,
|
||||
expected_sign_date,
|
||||
owner_user_id,
|
||||
remark
|
||||
) values (
|
||||
#{request.channelName},
|
||||
#{request.province},
|
||||
#{request.industry},
|
||||
#{request.annualRevenue},
|
||||
#{request.staffSize},
|
||||
#{request.contactName},
|
||||
#{request.contactTitle},
|
||||
#{request.contactMobile},
|
||||
#{request.stage},
|
||||
#{request.landedFlag},
|
||||
#{request.expectedSignDate},
|
||||
#{userId},
|
||||
#{request.remark}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="updateSalesExpansion">
|
||||
update crm_sales_expansion
|
||||
set candidate_name = #{request.candidateName},
|
||||
mobile = #{request.mobile},
|
||||
email = #{request.email},
|
||||
target_dept_id = #{request.targetDeptId},
|
||||
industry = #{request.industry},
|
||||
title = #{request.title},
|
||||
intent_level = #{request.intentLevel},
|
||||
stage = #{request.stage},
|
||||
has_desktop_exp = #{request.hasDesktopExp},
|
||||
in_progress = #{request.inProgress},
|
||||
employment_status = #{request.employmentStatus},
|
||||
expected_join_date = #{request.expectedJoinDate},
|
||||
remark = #{request.remark}
|
||||
where id = #{id}
|
||||
and owner_user_id = #{userId}
|
||||
</update>
|
||||
|
||||
<update id="updateChannelExpansion">
|
||||
update crm_channel_expansion
|
||||
set channel_name = #{request.channelName},
|
||||
province = #{request.province},
|
||||
industry = #{request.industry},
|
||||
annual_revenue = #{request.annualRevenue},
|
||||
staff_size = #{request.staffSize},
|
||||
contact_name = #{request.contactName},
|
||||
contact_title = #{request.contactTitle},
|
||||
contact_mobile = #{request.contactMobile},
|
||||
stage = #{request.stage},
|
||||
landed_flag = #{request.landedFlag},
|
||||
expected_sign_date = #{request.expectedSignDate},
|
||||
remark = #{request.remark}
|
||||
where id = #{id}
|
||||
and owner_user_id = #{userId}
|
||||
</update>
|
||||
|
||||
<select id="countOwnedSalesExpansion" resultType="int">
|
||||
select count(1)
|
||||
from crm_sales_expansion
|
||||
where id = #{id}
|
||||
and owner_user_id = #{userId}
|
||||
</select>
|
||||
|
||||
<select id="countOwnedChannelExpansion" resultType="int">
|
||||
select count(1)
|
||||
from crm_channel_expansion
|
||||
where id = #{id}
|
||||
and owner_user_id = #{userId}
|
||||
</select>
|
||||
|
||||
<insert id="insertExpansionFollowUp">
|
||||
insert into crm_expansion_followup (
|
||||
biz_type,
|
||||
biz_id,
|
||||
followup_time,
|
||||
followup_type,
|
||||
content,
|
||||
next_action,
|
||||
followup_user_id
|
||||
) values (
|
||||
#{bizType},
|
||||
#{bizId},
|
||||
#{request.followUpTime},
|
||||
#{request.followUpType},
|
||||
#{request.content},
|
||||
#{request.nextAction},
|
||||
#{userId}
|
||||
)
|
||||
</insert>
|
||||
</mapper>
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.unis.crm.mapper.OpportunityMapper">
|
||||
|
||||
<select id="selectOpportunities" resultType="com.unis.crm.dto.opportunity.OpportunityItemDTO">
|
||||
select
|
||||
o.id,
|
||||
o.opportunity_code as code,
|
||||
o.opportunity_name as name,
|
||||
coalesce(c.customer_name, '未命名客户') as client,
|
||||
coalesce(u.display_name, '当前用户') as owner,
|
||||
o.amount,
|
||||
to_char(o.expected_close_date, 'YYYY-MM-DD') as date,
|
||||
o.confidence_pct as confidence,
|
||||
case coalesce(o.stage, 'initial_contact')
|
||||
when 'initial_contact' then '初步沟通'
|
||||
when 'solution_discussion' then '方案交流'
|
||||
when 'bidding' then '招投标'
|
||||
when 'business_negotiation' then '商务谈判'
|
||||
when 'won' then '已成交'
|
||||
when 'lost' then '已放弃'
|
||||
else coalesce(o.stage, '初步沟通')
|
||||
end as stage,
|
||||
coalesce(o.opportunity_type, '新建') as type,
|
||||
coalesce(o.pushed_to_oms, false) as pushedToOms,
|
||||
coalesce(o.product_type, 'VDI云桌面') as product,
|
||||
coalesce(o.source, '主动开发') as source,
|
||||
coalesce(o.description, '') as notes
|
||||
from crm_opportunity o
|
||||
left join crm_customer c on c.id = o.customer_id
|
||||
left join sys_user u on u.user_id = o.owner_user_id
|
||||
where o.owner_user_id = #{userId}
|
||||
<if test="keyword != null and keyword != ''">
|
||||
and (
|
||||
o.opportunity_name ilike concat('%', #{keyword}, '%')
|
||||
or o.opportunity_code ilike concat('%', #{keyword}, '%')
|
||||
or coalesce(c.customer_name, '') ilike concat('%', #{keyword}, '%')
|
||||
)
|
||||
</if>
|
||||
<if test="stage != null and stage != ''">
|
||||
and o.stage = #{stage}
|
||||
</if>
|
||||
order by coalesce(o.updated_at, o.created_at) desc, o.id desc
|
||||
</select>
|
||||
|
||||
<select id="selectOpportunityFollowUps" resultType="com.unis.crm.dto.opportunity.OpportunityFollowUpDTO">
|
||||
select
|
||||
f.id,
|
||||
f.opportunity_id as opportunityId,
|
||||
to_char(f.followup_time, 'YYYY-MM-DD HH24:MI') as date,
|
||||
coalesce(f.followup_type, '无') as type,
|
||||
coalesce(f.content, '无') as content,
|
||||
coalesce(u.display_name, '无') as user
|
||||
from crm_opportunity_followup f
|
||||
join crm_opportunity o on o.id = f.opportunity_id
|
||||
left join sys_user u on u.user_id = f.followup_user_id
|
||||
where o.owner_user_id = #{userId}
|
||||
and f.opportunity_id in
|
||||
<foreach collection="opportunityIds" item="id" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
order by f.followup_time desc, f.id desc
|
||||
</select>
|
||||
|
||||
<select id="selectOwnedCustomerIdByName" resultType="java.lang.Long">
|
||||
select id
|
||||
from crm_customer
|
||||
where owner_user_id = #{userId}
|
||||
and customer_name = #{customerName}
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<insert id="insertCustomer">
|
||||
insert into crm_customer (
|
||||
id,
|
||||
customer_code,
|
||||
customer_name,
|
||||
owner_user_id,
|
||||
source,
|
||||
status,
|
||||
created_at,
|
||||
updated_at
|
||||
) values (
|
||||
#{id},
|
||||
'CUS-' || to_char(current_date, 'YYYYMMDD') || '-' || lpad((coalesce((select count(1) from crm_customer), 0) + 1)::text, 3, '0'),
|
||||
#{customerName},
|
||||
#{userId},
|
||||
coalesce(#{source}, '主动开发'),
|
||||
'following',
|
||||
now(),
|
||||
now()
|
||||
)
|
||||
</insert>
|
||||
|
||||
<insert id="insertOpportunity" useGeneratedKeys="true" keyProperty="request.id">
|
||||
insert into crm_opportunity (
|
||||
opportunity_code,
|
||||
opportunity_name,
|
||||
customer_id,
|
||||
owner_user_id,
|
||||
amount,
|
||||
expected_close_date,
|
||||
confidence_pct,
|
||||
stage,
|
||||
opportunity_type,
|
||||
product_type,
|
||||
source,
|
||||
pushed_to_oms,
|
||||
oms_push_time,
|
||||
description,
|
||||
status,
|
||||
created_at,
|
||||
updated_at
|
||||
) values (
|
||||
'OPP-' || to_char(current_date, 'YYYYMMDD') || '-' || lpad((coalesce((select count(1) from crm_opportunity), 0) + 1)::text, 3, '0'),
|
||||
#{request.opportunityName},
|
||||
#{customerId},
|
||||
#{userId},
|
||||
#{request.amount},
|
||||
#{request.expectedCloseDate},
|
||||
#{request.confidencePct},
|
||||
#{request.stage},
|
||||
#{request.opportunityType},
|
||||
#{request.productType},
|
||||
#{request.source},
|
||||
#{request.pushedToOms},
|
||||
case when #{request.pushedToOms} then now() else null end,
|
||||
#{request.description},
|
||||
case
|
||||
when #{request.stage} = 'won' then 'won'
|
||||
when #{request.stage} = 'lost' then 'lost'
|
||||
else 'active'
|
||||
end,
|
||||
now(),
|
||||
now()
|
||||
)
|
||||
</insert>
|
||||
|
||||
<select id="countOwnedOpportunity" resultType="int">
|
||||
select count(1)
|
||||
from crm_opportunity
|
||||
where id = #{id}
|
||||
and owner_user_id = #{userId}
|
||||
</select>
|
||||
|
||||
<update id="updateOpportunity">
|
||||
update crm_opportunity
|
||||
set opportunity_name = #{request.opportunityName},
|
||||
customer_id = #{customerId},
|
||||
amount = #{request.amount},
|
||||
expected_close_date = #{request.expectedCloseDate},
|
||||
confidence_pct = #{request.confidencePct},
|
||||
stage = #{request.stage},
|
||||
opportunity_type = #{request.opportunityType},
|
||||
product_type = #{request.productType},
|
||||
source = #{request.source},
|
||||
pushed_to_oms = #{request.pushedToOms},
|
||||
oms_push_time = case
|
||||
when #{request.pushedToOms} then coalesce(oms_push_time, now())
|
||||
else null
|
||||
end,
|
||||
description = #{request.description},
|
||||
status = case
|
||||
when #{request.stage} = 'won' then 'won'
|
||||
when #{request.stage} = 'lost' then 'lost'
|
||||
else 'active'
|
||||
end,
|
||||
updated_at = now()
|
||||
where id = #{opportunityId}
|
||||
and owner_user_id = #{userId}
|
||||
</update>
|
||||
|
||||
<insert id="insertOpportunityFollowUp">
|
||||
insert into crm_opportunity_followup (
|
||||
opportunity_id,
|
||||
followup_time,
|
||||
followup_type,
|
||||
content,
|
||||
next_action,
|
||||
followup_user_id,
|
||||
created_at,
|
||||
updated_at
|
||||
) values (
|
||||
#{opportunityId},
|
||||
#{request.followUpTime},
|
||||
#{request.followUpType},
|
||||
#{request.content},
|
||||
#{request.nextAction},
|
||||
#{userId},
|
||||
now(),
|
||||
now()
|
||||
)
|
||||
</insert>
|
||||
|
||||
</mapper>
|
||||
|
|
@ -0,0 +1,374 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.unis.crm.mapper.WorkMapper">
|
||||
|
||||
<select id="selectTodayCheckIn" resultType="com.unis.crm.dto.work.WorkCheckInDTO">
|
||||
select
|
||||
id,
|
||||
to_char(checkin_date, 'YYYY-MM-DD') as date,
|
||||
to_char(checkin_time, 'HH24:MI') as time,
|
||||
coalesce(location_text, '') as locationText,
|
||||
coalesce(remark, '') as remark,
|
||||
coalesce(status, 'normal') as status,
|
||||
longitude,
|
||||
latitude
|
||||
from work_checkin
|
||||
where user_id = #{userId}
|
||||
and checkin_date = current_date
|
||||
order by checkin_time desc nulls last, id desc
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<select id="selectTodayReport" resultType="com.unis.crm.dto.work.WorkDailyReportDTO">
|
||||
select
|
||||
r.id,
|
||||
to_char(r.report_date, 'YYYY-MM-DD') as date,
|
||||
to_char(r.submit_time, 'YYYY-MM-DD HH24:MI') as submitTime,
|
||||
coalesce(r.work_content, '') as workContent,
|
||||
coalesce(r.tomorrow_plan, '') as tomorrowPlan,
|
||||
coalesce(r.source_type, 'manual') as sourceType,
|
||||
coalesce(r.status, 'submitted') as status,
|
||||
c.score,
|
||||
c.comment_content as comment
|
||||
from work_daily_report r
|
||||
left join (
|
||||
select distinct on (report_id)
|
||||
report_id,
|
||||
score,
|
||||
comment_content
|
||||
from work_daily_report_comment
|
||||
order by report_id, reviewed_at desc nulls last, id desc
|
||||
) c on c.report_id = r.id
|
||||
where r.user_id = #{userId}
|
||||
and r.report_date = current_date
|
||||
order by r.submit_time desc nulls last, r.id desc
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<select id="selectTodayWorkContentLines" resultType="java.lang.String">
|
||||
select line
|
||||
from (
|
||||
select
|
||||
coalesce(s.created_at, now()) as action_time,
|
||||
'新增销售拓展:' ||
|
||||
coalesce(s.candidate_name, '未命名') ||
|
||||
case
|
||||
when s.title is not null and btrim(s.title) <> '' then ',岗位:' || s.title
|
||||
else ''
|
||||
end ||
|
||||
case
|
||||
when s.intent_level is not null and btrim(s.intent_level) <> '' then ',意向:' || s.intent_level
|
||||
else ''
|
||||
end as line
|
||||
from crm_sales_expansion s
|
||||
where s.owner_user_id = #{userId}
|
||||
and s.created_at::date = current_date
|
||||
|
||||
union all
|
||||
|
||||
select
|
||||
coalesce(c.created_at, now()) as action_time,
|
||||
'新增渠道拓展:' ||
|
||||
coalesce(c.channel_name, '未命名渠道') ||
|
||||
case
|
||||
when c.province is not null and btrim(c.province) <> '' then ',地区:' || c.province
|
||||
else ''
|
||||
end ||
|
||||
case
|
||||
when c.industry is not null and btrim(c.industry) <> '' then ',行业:' || c.industry
|
||||
else ''
|
||||
end as line
|
||||
from crm_channel_expansion c
|
||||
where c.owner_user_id = #{userId}
|
||||
and c.created_at::date = current_date
|
||||
|
||||
union all
|
||||
|
||||
select
|
||||
coalesce(o.created_at, now()) as action_time,
|
||||
'新增商机:' ||
|
||||
coalesce(o.opportunity_name, '未命名商机') ||
|
||||
case
|
||||
when cust.customer_name is not null and btrim(cust.customer_name) <> '' then ',客户:' || cust.customer_name
|
||||
else ''
|
||||
end ||
|
||||
case
|
||||
when o.amount is not null then ',金额:¥' || trim(to_char(o.amount, 'FM9999999999990.00'))
|
||||
else ''
|
||||
end as line
|
||||
from crm_opportunity o
|
||||
left join crm_customer cust on cust.id = o.customer_id
|
||||
where o.owner_user_id = #{userId}
|
||||
and o.created_at::date = current_date
|
||||
|
||||
union all
|
||||
|
||||
select
|
||||
coalesce(f.followup_time, now()) as action_time,
|
||||
'销售拓展跟进:' ||
|
||||
coalesce(s.candidate_name, '未命名') ||
|
||||
case
|
||||
when f.followup_type is not null and btrim(f.followup_type) <> '' then ',方式:' || f.followup_type
|
||||
else ''
|
||||
end ||
|
||||
case
|
||||
when f.content is not null and btrim(f.content) <> '' then ',内容:' || f.content
|
||||
else ''
|
||||
end as line
|
||||
from crm_expansion_followup f
|
||||
join crm_sales_expansion s on s.id = f.biz_id and f.biz_type = 'sales'
|
||||
where f.followup_user_id = #{userId}
|
||||
and f.followup_time::date = current_date
|
||||
|
||||
union all
|
||||
|
||||
select
|
||||
coalesce(f.followup_time, now()) as action_time,
|
||||
'渠道拓展跟进:' ||
|
||||
coalesce(c.channel_name, '未命名渠道') ||
|
||||
case
|
||||
when f.followup_type is not null and btrim(f.followup_type) <> '' then ',方式:' || f.followup_type
|
||||
else ''
|
||||
end ||
|
||||
case
|
||||
when f.content is not null and btrim(f.content) <> '' then ',内容:' || f.content
|
||||
else ''
|
||||
end as line
|
||||
from crm_expansion_followup f
|
||||
join crm_channel_expansion c on c.id = f.biz_id and f.biz_type = 'channel'
|
||||
where f.followup_user_id = #{userId}
|
||||
and f.followup_time::date = current_date
|
||||
|
||||
union all
|
||||
|
||||
select
|
||||
coalesce(f.followup_time, now()) as action_time,
|
||||
'商机跟进:' ||
|
||||
coalesce(o.opportunity_name, '未命名商机') ||
|
||||
case
|
||||
when f.followup_type is not null and btrim(f.followup_type) <> '' then ',方式:' || f.followup_type
|
||||
else ''
|
||||
end ||
|
||||
case
|
||||
when f.content is not null and btrim(f.content) <> '' then ',内容:' || f.content
|
||||
else ''
|
||||
end as line
|
||||
from crm_opportunity_followup f
|
||||
join crm_opportunity o on o.id = f.opportunity_id
|
||||
where f.followup_user_id = #{userId}
|
||||
and f.followup_time::date = current_date
|
||||
) work_lines
|
||||
order by action_time asc, line asc
|
||||
</select>
|
||||
|
||||
<select id="selectHistory" resultType="com.unis.crm.dto.work.WorkHistoryItemDTO">
|
||||
select
|
||||
id,
|
||||
type,
|
||||
date,
|
||||
time,
|
||||
content,
|
||||
status,
|
||||
score,
|
||||
comment
|
||||
from (
|
||||
select
|
||||
c.id,
|
||||
'外勤打卡' as type,
|
||||
to_char(c.checkin_date, 'YYYY-MM-DD') as date,
|
||||
to_char(c.checkin_time, 'HH24:MI') as time,
|
||||
coalesce(c.location_text, '') ||
|
||||
case
|
||||
when c.remark is not null and btrim(c.remark) <> '' then E'\n备注:' || c.remark
|
||||
else ''
|
||||
end as content,
|
||||
case coalesce(c.status, 'normal')
|
||||
when 'normal' then '正常'
|
||||
when 'updated' then '已更新'
|
||||
else coalesce(c.status, '正常')
|
||||
end as status,
|
||||
null::integer as score,
|
||||
null::text as comment,
|
||||
coalesce(c.checkin_date::timestamp + c.checkin_time::time, c.created_at) as sort_time
|
||||
from work_checkin c
|
||||
where c.user_id = #{userId}
|
||||
|
||||
union all
|
||||
|
||||
select
|
||||
r.id,
|
||||
'日报' as type,
|
||||
to_char(r.report_date, 'YYYY-MM-DD') as date,
|
||||
to_char(r.submit_time, 'HH24:MI') as time,
|
||||
coalesce(r.work_content, '') ||
|
||||
case
|
||||
when r.tomorrow_plan is not null and btrim(r.tomorrow_plan) <> '' then E'\n明日计划:' || r.tomorrow_plan
|
||||
else ''
|
||||
end as content,
|
||||
case coalesce(rc.comment_content, '')
|
||||
when '' then
|
||||
case coalesce(r.status, 'submitted')
|
||||
when 'submitted' then '已提交'
|
||||
when 'reviewed' then '已点评'
|
||||
else coalesce(r.status, '已提交')
|
||||
end
|
||||
else '已点评'
|
||||
end as status,
|
||||
rc.score,
|
||||
rc.comment_content as comment,
|
||||
coalesce(r.report_date::timestamp + r.submit_time::time, r.created_at) as sort_time
|
||||
from work_daily_report r
|
||||
left join (
|
||||
select distinct on (report_id)
|
||||
report_id,
|
||||
score,
|
||||
comment_content
|
||||
from work_daily_report_comment
|
||||
order by report_id, reviewed_at desc nulls last, id desc
|
||||
) rc on rc.report_id = r.id
|
||||
where r.user_id = #{userId}
|
||||
) history
|
||||
order by sort_time desc nulls last, id desc
|
||||
</select>
|
||||
|
||||
<select id="selectTodayCheckInId" resultType="java.lang.Long">
|
||||
select id
|
||||
from work_checkin
|
||||
where user_id = #{userId}
|
||||
and checkin_date = current_date
|
||||
order by checkin_time desc nulls last, id desc
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<insert id="insertCheckIn">
|
||||
insert into work_checkin (
|
||||
id,
|
||||
user_id,
|
||||
checkin_date,
|
||||
checkin_time,
|
||||
longitude,
|
||||
latitude,
|
||||
location_text,
|
||||
remark,
|
||||
status,
|
||||
created_at,
|
||||
updated_at
|
||||
) values (
|
||||
(select coalesce(max(id), 0) + 1 from work_checkin),
|
||||
#{userId},
|
||||
current_date,
|
||||
now(),
|
||||
#{request.longitude},
|
||||
#{request.latitude},
|
||||
#{request.locationText},
|
||||
#{request.remark},
|
||||
'normal',
|
||||
now(),
|
||||
now()
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="updateCheckIn">
|
||||
update work_checkin
|
||||
set checkin_time = now(),
|
||||
longitude = #{request.longitude},
|
||||
latitude = #{request.latitude},
|
||||
location_text = #{request.locationText},
|
||||
remark = #{request.remark},
|
||||
status = 'normal',
|
||||
updated_at = now()
|
||||
where id = #{checkInId}
|
||||
</update>
|
||||
|
||||
<select id="selectTodayReportId" resultType="java.lang.Long">
|
||||
select id
|
||||
from work_daily_report
|
||||
where user_id = #{userId}
|
||||
and report_date = current_date
|
||||
order by submit_time desc nulls last, id desc
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<insert id="insertDailyReport">
|
||||
insert into work_daily_report (
|
||||
user_id,
|
||||
report_date,
|
||||
work_content,
|
||||
tomorrow_plan,
|
||||
source_type,
|
||||
submit_time,
|
||||
status,
|
||||
created_at,
|
||||
updated_at
|
||||
) values (
|
||||
#{userId},
|
||||
current_date,
|
||||
#{request.workContent},
|
||||
#{request.tomorrowPlan},
|
||||
#{request.sourceType},
|
||||
now(),
|
||||
'submitted',
|
||||
now(),
|
||||
now()
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="updateDailyReport">
|
||||
update work_daily_report
|
||||
set work_content = #{request.workContent},
|
||||
tomorrow_plan = #{request.tomorrowPlan},
|
||||
source_type = #{request.sourceType},
|
||||
submit_time = now(),
|
||||
status = 'submitted',
|
||||
updated_at = now()
|
||||
where id = #{reportId}
|
||||
</update>
|
||||
|
||||
<select id="selectTodoIdByBiz" resultType="java.lang.Long">
|
||||
select id
|
||||
from work_todo
|
||||
where user_id = #{userId}
|
||||
and biz_type = #{bizType}
|
||||
and biz_id = #{bizId}
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<insert id="insertTodo">
|
||||
insert into work_todo (
|
||||
id,
|
||||
user_id,
|
||||
title,
|
||||
biz_type,
|
||||
biz_id,
|
||||
due_date,
|
||||
status,
|
||||
priority,
|
||||
created_at,
|
||||
updated_at
|
||||
) values (
|
||||
#{todoId},
|
||||
#{userId},
|
||||
#{title},
|
||||
#{bizType},
|
||||
#{bizId},
|
||||
current_date::timestamp + interval '1 day' + time '09:00',
|
||||
'todo',
|
||||
'medium',
|
||||
now(),
|
||||
now()
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="updateTodo">
|
||||
update work_todo
|
||||
set title = #{title},
|
||||
due_date = current_date::timestamp + interval '1 day' + time '09:00',
|
||||
status = 'todo',
|
||||
priority = 'medium',
|
||||
updated_at = now()
|
||||
where id = #{todoId}
|
||||
</update>
|
||||
|
||||
</mapper>
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
server:
|
||||
port: 8080
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: unis-crm-backend
|
||||
datasource:
|
||||
url: jdbc:postgresql://127.0.0.1:5432/nex_auth
|
||||
username: postgres
|
||||
password: 199628
|
||||
driver-class-name: org.postgresql.Driver
|
||||
data:
|
||||
redis:
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
password: 199628@tlw
|
||||
database: 14
|
||||
|
||||
mybatis-plus:
|
||||
mapper-locations: classpath*:/mapper/**/*.xml
|
||||
type-aliases-package: com.unis.crm.dto.dashboard
|
||||
configuration:
|
||||
map-underscore-to-camel-case: true
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
|
||||
logging:
|
||||
level:
|
||||
com.unis.crm: info
|
||||
|
||||
unisbase:
|
||||
tenant:
|
||||
enabled: false
|
||||
web:
|
||||
auth-endpoints-enabled: true
|
||||
management-endpoints-enabled: true
|
||||
security:
|
||||
enabled: true
|
||||
mode: embedded
|
||||
jwt-secret: change-me-please-change-me-32bytes
|
||||
auth-header: Authorization
|
||||
token-prefix: "Bearer "
|
||||
permit-all-urls:
|
||||
- /actuator/health
|
||||
internal-auth:
|
||||
enabled: true
|
||||
secret: change-me-internal-secret
|
||||
header-name: X-Internal-Secret
|
||||
app:
|
||||
upload-path: /Users/kangwenjing/Downloads/crm/uploads
|
||||
resource-prefix: /sys/api/static/
|
||||
captcha:
|
||||
ttl-seconds: 120
|
||||
max-attempts: 5
|
||||
token:
|
||||
access-default-minutes: 30
|
||||
refresh-default-days: 7
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue