chore: initial commit
This commit is contained in:
5
.dockerignore
Normal file
5
.dockerignore
Normal file
@@ -0,0 +1,5 @@
|
||||
**/node_modules/
|
||||
**/.node/
|
||||
**/dist/
|
||||
**/pnpm-lock.yaml
|
||||
|
||||
71
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
71
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
name: BUG 提交
|
||||
description: 提交产品缺陷帮助我们更好的改进
|
||||
title: "[BUG] "
|
||||
labels: ["bug"]
|
||||
assignees: ["zrfit"]
|
||||
body:
|
||||
- type: markdown
|
||||
id: contacts_title
|
||||
attributes:
|
||||
value: "## 联系方式"
|
||||
- type: input
|
||||
id: contacts
|
||||
validations:
|
||||
required: false
|
||||
attributes:
|
||||
label: "联系方式"
|
||||
description: "可以快速联系到您进一步沟通的方式:交流群号及昵称、邮箱等"
|
||||
- type: markdown
|
||||
id: environment
|
||||
attributes:
|
||||
value: "## 环境信息"
|
||||
- type: input
|
||||
id: version
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "Cordys CRM 版本"
|
||||
description: "在系统右上角的下拉菜单中选择 `关于` 可查看当前 Cordys CRM 版本。"
|
||||
- type: dropdown
|
||||
id: database
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "使用外置数据库"
|
||||
description: "如果使用外置数据库,请确认您的外置数据库版本,目前仅支持 MySQL 8.x 与 MariaDB 11.x。"
|
||||
options:
|
||||
- 否
|
||||
- 是
|
||||
- type: markdown
|
||||
id: details
|
||||
attributes:
|
||||
value: "## 详细信息"
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: "问题描述"
|
||||
description: "简要描述您碰到的问题"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: how-happened
|
||||
attributes:
|
||||
label: "重现步骤"
|
||||
description: "如果操作可以重现该问题"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expect
|
||||
attributes:
|
||||
label: "期待的正确结果"
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: "相关日志输出"
|
||||
description: "请复制并粘贴任何相关的日志输出。 这将自动格式化为代码,因此无需反引号。"
|
||||
render: shell
|
||||
- type: textarea
|
||||
id: additional-information
|
||||
attributes:
|
||||
label: "附加信息"
|
||||
description: "如果你还有其他需要提供的信息,可以在这里填写(可以提供截图、视频等)。"
|
||||
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 对 Cordys CRM 项目有其他问题
|
||||
url: https://github.com/1Panel-dev/CordysCRM/
|
||||
about: 如果想要进一步了解 Cordys CRM 项目,欢迎发送邮件到 support@fit2cloud.com 进行提问。
|
||||
- name: 反馈安全问题 (Report Security Bug)
|
||||
url: mailto://support@fit2cloud.com
|
||||
about: 请通过邮箱 support@fit2cloud.com 反馈安全问题 (Please report security vulnerabilities to support@fit2cloud.com)
|
||||
43
.github/ISSUE_TEMPLATE/feature.yml
vendored
Normal file
43
.github/ISSUE_TEMPLATE/feature.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: 需求建议
|
||||
description: 提出针对本项目的想法和建议
|
||||
title: "[FEATURE] "
|
||||
labels: ["需求"]
|
||||
assignees: ["luty2018", "sakura1412"]
|
||||
body:
|
||||
- type: markdown
|
||||
id: environment
|
||||
attributes:
|
||||
value: "## 环境信息"
|
||||
- type: input
|
||||
id: version
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "Cordys CRM 版本"
|
||||
description: "在系统右上角的下拉菜单中选择 `关于` 可查看当前 Cordys CRM 版本。"
|
||||
- type: input
|
||||
id: contacts
|
||||
validations:
|
||||
required: false
|
||||
attributes:
|
||||
label: "联系方式"
|
||||
description: "可以快速联系到您进一步沟通的方式:交流群号及昵称、邮箱等"
|
||||
- type: markdown
|
||||
id: details
|
||||
attributes:
|
||||
value: "## 详细信息"
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: "请描述您的需求或者改进建议"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: "请描述你建议的实现方案"
|
||||
- type: textarea
|
||||
id: additional-information
|
||||
attributes:
|
||||
label: "附加信息"
|
||||
description: "如果你还有其他需要提供的信息,可以在这里填写(可以提供截图、视频等)。"
|
||||
12
.github/ISSUE_TEMPLATE/question.yml
vendored
Normal file
12
.github/ISSUE_TEMPLATE/question.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
name: 问题咨询
|
||||
description: 提出针对本项目安装部署、使用及其他方面的相关问题
|
||||
title: "[QUESTION] "
|
||||
labels: ["question"]
|
||||
assignees: ["luty2018", "sakura1412"]
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: "请描述您的问题"
|
||||
validations:
|
||||
required: true
|
||||
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
#### What this PR does / why we need it?
|
||||
|
||||
#### Summary of your change
|
||||
|
||||
#### Please indicate you've done the following:
|
||||
|
||||
- [ ] Made sure tests are passing and test coverage is added if needed.
|
||||
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
|
||||
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
|
||||
38
.github/workflows/add-labels-for-pr.yml
vendored
Normal file
38
.github/workflows/add-labels-for-pr.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
name: Add Labels to PR
|
||||
|
||||
jobs:
|
||||
add_labels:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
# Checkout the repository code
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Extract and format the labels from the comment as "username: label"
|
||||
- name: Extract reviewer and labels
|
||||
if: ${{ github.event_name == 'issue_comment' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/') }}
|
||||
id: extract_labels
|
||||
run: |
|
||||
comment_body="${{ github.event.comment.body }}"
|
||||
reviewer="${{ github.event.comment.user.login }}"
|
||||
# Clean up comment and extract labels
|
||||
raw_labels=$(echo "$comment_body" | sed 's|^/||')
|
||||
formatted_labels=$(echo "$raw_labels" | awk -v reviewer="$reviewer" '{split($0, arr, ","); for (i in arr) {gsub(/^ +| +$/, "", arr[i]); print reviewer ": " arr[i]}}')
|
||||
|
||||
# Set the formatted labels as an environment variable (new way, not using set-output)
|
||||
echo "labels=$formatted_labels" >> $GITHUB_ENV
|
||||
|
||||
# Output labels for debugging
|
||||
echo "Formatted labels: $formatted_labels"
|
||||
|
||||
# Add the extracted labels to the PR
|
||||
- name: Add labels via PR comment
|
||||
if: ${{ github.event_name == 'issue_comment' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/') }}
|
||||
uses: actions-ecosystem/action-add-labels@v1
|
||||
with:
|
||||
github_token: ${{ secrets.GH_TOKEN }}
|
||||
labels: ${{ env.labels }}
|
||||
52
.github/workflows/build-and-push-base.yml
vendored
Normal file
52
.github/workflows/build-and-push-base.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
name: build-and-push-base
|
||||
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
architecture:
|
||||
description: 'Architecture'
|
||||
required: true
|
||||
default: 'linux/amd64'
|
||||
type: choice
|
||||
options:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
- linux/amd64,linux/arm64
|
||||
|
||||
jobs:
|
||||
build-and-push-base-to-ghcr:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
- name: Prepare
|
||||
id: prepare
|
||||
run: |
|
||||
DOCKER_IMAGE=ghcr.io/cordys-dev/cordys-base
|
||||
DOCKER_PLATFORMS=${{ github.event.inputs.architecture }}
|
||||
TAG_NAME=latest
|
||||
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME} --tag ${DOCKER_IMAGE}:latest"
|
||||
echo ::set-output name=docker_image::${DOCKER_IMAGE}
|
||||
echo ::set-output name=version::${TAG_NAME}
|
||||
echo ::set-output name=buildx_args::--platform ${DOCKER_PLATFORMS} --no-cache \
|
||||
--build-arg VERSION=${TAG_NAME} \
|
||||
--build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \
|
||||
--build-arg VCS_REF=${GITHUB_SHA::8} \
|
||||
${DOCKER_IMAGE_TAGS} .
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GH_TOKEN }}
|
||||
- name: Docker Buildx (build-and-push)
|
||||
run: |
|
||||
docker buildx build --output "type=image,push=true" ${{ steps.prepare.outputs.buildx_args }} -f installer/Dockerfile.base
|
||||
|
||||
108
.github/workflows/build-and-push.yml
vendored
Normal file
108
.github/workflows/build-and-push.yml
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
name: build-and-push
|
||||
|
||||
run-name: 构建镜像并推送仓库 ${{ github.event.inputs.dockerImageTag }} (${{ github.event.inputs.registry }})
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
dockerImageTag:
|
||||
description: 'Docker Image Tag'
|
||||
default: 'dev'
|
||||
required: true
|
||||
architecture:
|
||||
description: 'Architecture'
|
||||
required: true
|
||||
default: 'linux/amd64'
|
||||
type: choice
|
||||
options:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
- linux/amd64,linux/arm64
|
||||
registry:
|
||||
description: 'Push To Registry'
|
||||
required: true
|
||||
default: 'fit2cloud-registry'
|
||||
type: choice
|
||||
options:
|
||||
- fit2cloud-registry
|
||||
|
||||
jobs:
|
||||
build-and-push-to-fit2cloud-registry:
|
||||
if: ${{ contains(github.event.inputs.registry, 'fit2cloud') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout specific repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: cordys-dev/cordys-crm
|
||||
token: ${{ secrets.GH_TOKEN }} # 使用 GitHub token
|
||||
|
||||
- name: Prepare
|
||||
id: prepare
|
||||
run: |
|
||||
DOCKER_IMAGE=${{ secrets.FIT2CLOUD_REGISTRY_HOST }}/cordys/cordys-crm-ce
|
||||
DOCKER_PLATFORMS=${{ github.event.inputs.architecture }}
|
||||
TAG_NAME=${{ github.event.inputs.dockerImageTag }}
|
||||
SHORT_SHA=$(git rev-parse --short HEAD)
|
||||
|
||||
if [[ ${TAG_NAME} == *dev* ]]; then
|
||||
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME}"
|
||||
else
|
||||
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME} --tag ${DOCKER_IMAGE}:latest"
|
||||
fi
|
||||
echo ::set-output name=buildx_args::--platform ${DOCKER_PLATFORMS} \
|
||||
--build-arg DOCKER_IMAGE_TAG=${{ github.event.inputs.dockerImageTag }} --build-arg BUILD_AT=$(TZ=Asia/Shanghai date +'%Y-%m-%dT%H:%M') --build-arg GITHUB_COMMIT=${GITHUB_SHA::8} --no-cache \
|
||||
--build-arg FIT2CLOUD_MAVEN_USERNAME=${{ secrets.FIT2CLOUD_MAVEN_USERNAME }} --build-arg FIT2CLOUD_MAVEN_PASSWORD=${{ secrets.FIT2CLOUD_MAVEN_PASSWORD }} \
|
||||
--build-arg CRM_VERSION=${TAG_NAME}-${SHORT_SHA} \
|
||||
${DOCKER_IMAGE_TAGS} .
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to FIT2CLOUD Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ secrets.FIT2CLOUD_REGISTRY_HOST }}
|
||||
username: ${{ secrets.FIT2CLOUD_REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.FIT2CLOUD_REGISTRY_PASSWORD }}
|
||||
|
||||
- name: Docker Buildx (build-and-push)
|
||||
run: |
|
||||
docker buildx build --output "type=image,push=true" ${{ steps.prepare.outputs.buildx_args }} -f installer/Dockerfile
|
||||
# 创建或更新标签
|
||||
- name: Create or Re-create Tag
|
||||
if: success()
|
||||
run: |
|
||||
git config --global user.name 'fit2-zhao'
|
||||
git config --global user.email 'yong.zhao@fit2cloud.com'
|
||||
|
||||
# 判断是否为dev标签,如果是则跳过打tag操作
|
||||
if [[ "${{ github.event.inputs.dockerImageTag }}" == "dev" ]]; then
|
||||
echo "当前为dev标签,跳过打tag操作"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 获取远程标签信息
|
||||
git fetch --prune --tags
|
||||
|
||||
# 检查标签是否存在(本地或远程)
|
||||
if git ls-remote --tags origin refs/tags/${{ github.event.inputs.dockerImageTag }} | grep -q "${{ github.event.inputs.dockerImageTag }}" || git tag -l "${{ github.event.inputs.dockerImageTag }}" | grep -q "${{ github.event.inputs.dockerImageTag }}"; then
|
||||
echo "标签 ${{ github.event.inputs.dockerImageTag }} 已存在,正在删除..."
|
||||
|
||||
# 删除本地标签(如果存在)
|
||||
git tag -d ${{ github.event.inputs.dockerImageTag }} || true
|
||||
|
||||
# 删除远程标签并确认结果
|
||||
git push --delete origin ${{ github.event.inputs.dockerImageTag }} || echo "远程标签可能不存在或已被删除"
|
||||
|
||||
# 确认标签已被删除
|
||||
sleep 2
|
||||
git fetch --prune --tags
|
||||
fi
|
||||
|
||||
# 创建和推送新标签
|
||||
git tag -a ${{ github.event.inputs.dockerImageTag }} -m "Release ${{ github.event.inputs.dockerImageTag }}"
|
||||
git push origin ${{ github.event.inputs.dockerImageTag }}
|
||||
46
.github/workflows/codecov.yml
vendored
Normal file
46
.github/workflows/codecov.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "backend/**"
|
||||
- "pom.xml"
|
||||
|
||||
name: Code Coverage
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
generic_handler:
|
||||
name: Code Coverage
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
# Cache Maven dependencies
|
||||
- name: Cache Maven dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('backend/pom.xml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-maven-
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 21
|
||||
check-latest: false
|
||||
|
||||
- name: Build with Maven - other
|
||||
run: mvn -B package -DskipAntRunForJenkins --file pom.xml -pl '!frontend'
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
slug: 1Panel-dev/CordysCRM
|
||||
17
.github/workflows/create-pr-from-push.yml
vendored
Normal file
17
.github/workflows/create-pr-from-push.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'pr@**'
|
||||
- 'repr@**'
|
||||
|
||||
name: Auto Create PR
|
||||
|
||||
jobs:
|
||||
generic_handler:
|
||||
name: Auto Create PR
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Create pull request
|
||||
uses: jumpserver/action-generic-handler@master
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
30
.github/workflows/frontend-build.yml
vendored
Normal file
30
.github/workflows/frontend-build.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "frontend/**"
|
||||
|
||||
name: Frontend Code Checking
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
generic_handler:
|
||||
name: Frontend Code Checking
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 21
|
||||
cache: 'maven'
|
||||
cache-dependency-path: '**/pom.xml' # optional
|
||||
check-latest: true
|
||||
- name: Build with Maven - only frontend
|
||||
run: mvn -T 1C -B package --file pom.xml -pl frontend
|
||||
16
.github/workflows/issue-close.yml
vendored
Normal file
16
.github/workflows/issue-close.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: Issue Close Check
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [closed]
|
||||
|
||||
jobs:
|
||||
issue-close-remove-labels:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Remove labels
|
||||
uses: actions-cool/issues-helper@v2
|
||||
if: ${{ !github.event.issue.pull_request }}
|
||||
with:
|
||||
actions: 'remove-labels'
|
||||
labels: '待处理'
|
||||
38
.github/workflows/issue-comment.yml
vendored
Normal file
38
.github/workflows/issue-comment.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
name: Add issues workflow labels
|
||||
|
||||
jobs:
|
||||
add-label-if-is-author:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ (github.event.issue.user.id == github.event.comment.user.id) && (!github.event.issue.pull_request) }}
|
||||
steps:
|
||||
- name: Add require handle label
|
||||
uses: actions-cool/issues-helper@v2
|
||||
with:
|
||||
actions: 'add-labels'
|
||||
labels: '待处理'
|
||||
|
||||
- name: Remove require reply label
|
||||
uses: actions-cool/issues-helper@v2
|
||||
with:
|
||||
actions: 'remove-labels'
|
||||
labels: '待用户反馈'
|
||||
|
||||
add-label-if-not-author:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ (github.event.issue.user.id != github.event.comment.user.id) && (!github.event.issue.pull_request) && (github.event.issue.state == 'open') }}
|
||||
steps:
|
||||
- name: Add require replay label
|
||||
uses: actions-cool/issues-helper@v2
|
||||
with:
|
||||
actions: 'add-labels'
|
||||
labels: '待用户反馈'
|
||||
|
||||
- name: Remove require handle label
|
||||
uses: actions-cool/issues-helper@v2
|
||||
with:
|
||||
actions: 'remove-labels'
|
||||
labels: '待处理'
|
||||
16
.github/workflows/issue-open.yml
vendored
Normal file
16
.github/workflows/issue-open.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: Issue Open Check
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
issue-open-add-labels:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Add labels
|
||||
uses: actions-cool/issues-helper@v2
|
||||
if: ${{ !github.event.issue.pull_request }}
|
||||
with:
|
||||
actions: 'add-labels'
|
||||
labels: '待处理'
|
||||
109
.github/workflows/issue-sync-to-tapd.yml
vendored
Normal file
109
.github/workflows/issue-sync-to-tapd.yml
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
name: Sync GitHub Issues to TAPD
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- assigned
|
||||
- reopened
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
sync-to-tapd:
|
||||
runs-on: ubuntu-latest
|
||||
concurrency:
|
||||
group: sync-issue-${{ github.event.issue.number }}
|
||||
cancel-in-progress: false
|
||||
|
||||
steps:
|
||||
- name: Check if sync is needed (assignee + live labels)
|
||||
id: check
|
||||
run: |
|
||||
ASSIGNEE="${{ github.event.issue.assignee.login }}"
|
||||
|
||||
# Fetch live labels from GitHub API (not the snapshot in the event)
|
||||
LABELS=$(gh issue view ${{ github.event.issue.number }} \
|
||||
--repo ${{ github.repository }} \
|
||||
--json labels \
|
||||
--jq '.labels[].name')
|
||||
|
||||
if [ -z "$ASSIGNEE" ] || [ "$ASSIGNEE" != "luty2018" ]; then
|
||||
echo "Issue is not assigned to luty2018 or has no assignee, skipping sync"
|
||||
echo "should_sync=false" >> $GITHUB_OUTPUT
|
||||
elif echo "$LABELS" | grep -qx "tapd-synced"; then
|
||||
echo "Issue already has the tapd-synced label, already synced, skipping"
|
||||
echo "should_sync=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "Issue is assigned to luty2018 and not yet synced, proceeding to sync"
|
||||
echo "should_sync=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
- name: Sync to TAPD
|
||||
if: steps.check.outputs.should_sync == 'true'
|
||||
env:
|
||||
ISSUE_TITLE: ${{ github.event.issue.title }}
|
||||
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
||||
ISSUE_URL: ${{ github.event.issue.html_url }}
|
||||
ISSUE_BODY: ${{ github.event.issue.body }}
|
||||
TAPD_WORKSPACE_ID: ${{ secrets.TAPD_WORKSPACE_ID }}
|
||||
TAPD_PROJECT_ID: ${{ secrets.TAPD_PROJECT_ID }}
|
||||
TAPD_ITERATION_ID: ${{ secrets.TAPD_ITERATION_ID }}
|
||||
TAPD_API_USERNAME: ${{ secrets.TAPD_API_USERNAME }}
|
||||
TAPD_API_PASSWORD: ${{ secrets.TAPD_API_PASSWORD }}
|
||||
TAPD_API_OWNER: ${{ secrets.TAPD_API_OWNER }}
|
||||
run: |
|
||||
# Fallback for empty body
|
||||
BODY="${ISSUE_BODY:-No description}"
|
||||
TITLE="[GitHub Issue #${ISSUE_NUMBER}] ${ISSUE_TITLE}"
|
||||
|
||||
BODY_HTML="${BODY//$'\n'/<br>}"
|
||||
DESCRIPTION="GitHub link: ${ISSUE_URL}<br><br>Description: ${BODY_HTML}"
|
||||
|
||||
JSON=$(jq -n \
|
||||
--arg name "$TITLE" \
|
||||
--arg desc "$DESCRIPTION" \
|
||||
--arg proj "$TAPD_PROJECT_ID" \
|
||||
--arg ws "$TAPD_WORKSPACE_ID" \
|
||||
--arg iteration "$TAPD_ITERATION_ID" \
|
||||
--arg user "$TAPD_API_USERNAME" \
|
||||
--arg owner "$TAPD_API_OWNER" \
|
||||
'{
|
||||
name: $name,
|
||||
description: $desc,
|
||||
project_id: $proj,
|
||||
workspace_id: $ws,
|
||||
owner: $owner,
|
||||
creator: $owner
|
||||
} + if $iteration != "" then {iteration_id: $iteration} else {} end')
|
||||
|
||||
echo "Creating TAPD story..."
|
||||
echo "Title: $TITLE"
|
||||
echo "Description: $DESCRIPTION"
|
||||
|
||||
HTTP_CODE=$(curl -s -o /dev/stderr -w "%{http_code}" -X POST \
|
||||
"https://api.tapd.cn/stories?workspace_id=${TAPD_WORKSPACE_ID}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-u "${TAPD_API_USERNAME}:${TAPD_API_PASSWORD}" \
|
||||
-d "$JSON")
|
||||
|
||||
if [ "$HTTP_CODE" -ne 200 ] && [ "$HTTP_CODE" -ne 201 ]; then
|
||||
echo "::error::TAPD API returned HTTP ${HTTP_CODE}, check the response details above"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "::notice::Sync completed, TAPD story created"
|
||||
|
||||
- name: Add synced label
|
||||
if: steps.check.outputs.should_sync == 'true'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
labels: ['tapd-synced']
|
||||
})
|
||||
41
.github/workflows/llm-code-review.yml
vendored
Normal file
41
.github/workflows/llm-code-review.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: LLM Code Review
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
llm-code-review:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
# 1️⃣ 拉代码
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# 2️⃣ LLM Code Review(发表评论)
|
||||
- name: Run LLM Code Review
|
||||
id: llm-code-review
|
||||
uses: fit2cloud/LLM-CodeReview-Action@main
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
OPENAI_API_KEY: ${{ secrets.ALIYUN_LLM_API_KEY }}
|
||||
OPENAI_API_ENDPOINT: https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
MODEL: qwen3-coder-plus
|
||||
LANGUAGE: Chinese
|
||||
PROMPT: |
|
||||
你是资深代码审查专家,严格检查以下代码差异。
|
||||
|
||||
⚠️【必须遵守】
|
||||
- 只在代码质量存在问题时发表评论。
|
||||
- 不要解释、不添加多余文本。
|
||||
- 审查应专注于以下内容:逻辑错误、代码规范、可读性、性能优化等。
|
||||
|
||||
temperature: 0.2
|
||||
top_p: 1
|
||||
MAX_PATCH_LENGTH: 50000
|
||||
IGNORE_PATTERNS: "/node_modules,*.md,/dist,/.github"
|
||||
FILE_PATTERNS: "*.java"
|
||||
15
.github/workflows/sync2gitee.yml
vendored
Normal file
15
.github/workflows/sync2gitee.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
name: Synchronize to Gitee
|
||||
on: [push]
|
||||
jobs:
|
||||
repo-sync:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Mirror the Github organization repos to Gitee.
|
||||
uses: Yikun/hub-mirror-action@master
|
||||
with:
|
||||
src: 'github/1Panel-dev'
|
||||
dst: 'gitee/fit2cloud-feizhiyun'
|
||||
dst_key: ${{ secrets.GITEE_PRIVATE_KEY }}
|
||||
dst_token: ${{ secrets.GITEE_TOKEN }}
|
||||
static_list: "CordysCRM"
|
||||
force_update: true
|
||||
23
.github/workflows/typos-check.yml
vendored
Normal file
23
.github/workflows/typos-check.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: Typos Checking
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
run:
|
||||
name: Spell Check with Typos
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Actions Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Check spelling
|
||||
uses: crate-ci/typos@master
|
||||
with:
|
||||
config: .github/workflows/typos-extend-exclude.toml
|
||||
2
.github/workflows/typos-extend-exclude.toml
vendored
Normal file
2
.github/workflows/typos-extend-exclude.toml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
[files]
|
||||
extend-exclude = ["*.json", "**/*.json", "**/*.js", "**/*.html", "**/*.xml", "**/config.ts", "**/*.sql", "**/*.conf", "**/en-US.ts", "OWNERS", "CODEOWNERS", "LICENSE", "NOTICE"]
|
||||
80
.gitignore
vendored
Normal file
80
.gitignore
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
############################
|
||||
# Environment & Config
|
||||
############################
|
||||
|
||||
# Local env files
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
.venv
|
||||
|
||||
# Maven
|
||||
.mvn
|
||||
!.mvn/maven-wrapper.properties
|
||||
.flattened-pom.xml
|
||||
|
||||
############################
|
||||
# Logs
|
||||
############################
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-error.log*
|
||||
|
||||
############################
|
||||
# IDE / Editor
|
||||
############################
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# IntelliJ IDEA
|
||||
.idea/*
|
||||
!.idea/icon.png
|
||||
**/*.iml
|
||||
|
||||
# Eclipse
|
||||
.settings
|
||||
.project
|
||||
.classpath
|
||||
.factorypath
|
||||
|
||||
# VS Code
|
||||
.vscode
|
||||
|
||||
# Visual Studio
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
|
||||
# Other
|
||||
*.sw?
|
||||
.history
|
||||
|
||||
############################
|
||||
# Build Output
|
||||
############################
|
||||
|
||||
target
|
||||
.jython_cache
|
||||
|
||||
############################
|
||||
# Static Resources (ignored)
|
||||
############################
|
||||
|
||||
src/main/resources/static
|
||||
src/main/resources/templates
|
||||
/backend/crm/src/main/resources/static/
|
||||
/backend/app/src/main/resources/static/
|
||||
|
||||
############################
|
||||
# Custom Files
|
||||
############################
|
||||
|
||||
qywx.json
|
||||
.gitattributes
|
||||
|
||||
# Claude Code
|
||||
.claude
|
||||
12
.typos.toml
Normal file
12
.typos.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[default.extend-words]
|
||||
AKE = "AKE"
|
||||
[default.extend-identifiers]
|
||||
maintain_column_froms = "maintain_column_froms"
|
||||
RegistCapi="RegistCapi"
|
||||
[files]
|
||||
extend-exclude = [
|
||||
"frontend/public",
|
||||
"frontend/packages/web/src/components/business/crm-city-select/config.ts",
|
||||
"backend/crm/src/main/resources/region/region.json",
|
||||
"iconfont.json"
|
||||
]
|
||||
49
BUILD.md
Normal file
49
BUILD.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# 构建过程说明
|
||||
|
||||
本项目包含多个模块,构建分为基础配置安装、后端构建、前端构建和整体打包四个部分。
|
||||
|
||||
---
|
||||
|
||||
## 🔧 1. 安装基础 POM
|
||||
|
||||
该命令会将 `parent pom` 安装到本地 Maven 仓库,使其他外部子工程可以获取最新的 `<properties>` 配置。
|
||||
|
||||
```bash
|
||||
# 如果遇到网络问题,可以使用阿里云镜像加速
|
||||
./mvnw install -N
|
||||
```
|
||||
这是多模块项目的必要步骤,确保所有子模块能正确继承父POM的配置。
|
||||
---
|
||||
|
||||
## 🖥️ 2. 后端构建
|
||||
|
||||
执行以下命令构建后端模块(如 `backend` 中的 `framework`、`crm`、`app` 等)并安装到本地仓库:
|
||||
|
||||
```bash
|
||||
./mvnw clean install -DskipTests -DskipAntRunForJenkins --file backend/pom.xml
|
||||
```
|
||||
|
||||
> ✅ 参数说明:
|
||||
>
|
||||
> * `-DskipTests`: 跳过测试用例执行。
|
||||
> * `-DskipAntRunForJenkins`: 跳过 Jenkins 使用的 Ant 任务。
|
||||
|
||||
---
|
||||
|
||||
## 💻 3. 前端构建
|
||||
|
||||
前端构建请参考 [`/frontend/REDEME.md`](./frontend/REDEME.md) 中的具体说明!
|
||||
|
||||
> 📌 提示:确保运行环境已正确安装 Node.js 和依赖环境!
|
||||
|
||||
---
|
||||
|
||||
## 📦 4. 整体打包
|
||||
|
||||
使用以下命令进行完整的构建与打包:
|
||||
|
||||
```bash
|
||||
./mvnw clean package
|
||||
```
|
||||
> ✅ 备注:
|
||||
> mvnw 为项目自带打包工具,也可使用本地部署的 mvn
|
||||
128
CODE_OF_CONDUCT.md
Normal file
128
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
support@fit2cloud.com.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
38
CONTRIBUTING.md
Normal file
38
CONTRIBUTING.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Contributing
|
||||
|
||||
As a contributor, please acknowledge and agree that:
|
||||
|
||||
- The project maintainers may adjust the open-source license terms as necessary to make them either more strict or more relaxed.
|
||||
- Any code you contribute may be used for commercial purposes, including but not limited to the project’s cloud-based operations.
|
||||
|
||||
|
||||
## Creating a Pull Request
|
||||
|
||||
Pull requests (PRs) are always welcome — even small ones that fix typos or minor issues.
|
||||
If your contribution involves significant work or new features, please open an issue first to start a discussion before you begin implementation.
|
||||
|
||||
Please break down your changes into small, focused PRs whenever possible.
|
||||
Large PRs with many features or extensive changes are difficult to review and may take longer to merge. Incremental submissions are strongly encouraged.
|
||||
|
||||
These development guidelines provide information about the repository structure, how to set up your development environment, how to run the project, and more.
|
||||
|
||||
**Note:** When splitting a feature into multiple PRs, please ensure that each individual PR can be safely merged into the `master` branch without breaking existing functionality.
|
||||
If a PR introduces incomplete or non-functional changes, it will not be merged until the feature is complete.
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
Reporting issues is a great way to contribute to the project.
|
||||
Well-written and detailed bug reports are always appreciated! Please open a new issue and fill out all required information using the provided template.
|
||||
|
||||
Before opening a new issue, please check the existing issue list to avoid duplicates.
|
||||
If a similar issue already exists, you can subscribe to it to receive updates.
|
||||
If you have additional information to share, please add it as a comment on that issue.
|
||||
|
||||
When reporting an issue, please include the following information:
|
||||
|
||||
- The version you are using
|
||||
- Steps to reproduce the problem
|
||||
- Screenshots or log files, if applicable
|
||||
|
||||
Because all issues are public, please ensure that you remove any sensitive information (e.g., usernames, passwords, IP addresses, or company names) from your submissions.
|
||||
Replace sensitive data with placeholders such as `REDACTED` or `****`.
|
||||
18
LICENSE
Normal file
18
LICENSE
Normal file
@@ -0,0 +1,18 @@
|
||||
This project is distributed under the GNU General Public License, version 3 (GPLv3),
|
||||
with the following additional terms and conditions:
|
||||
|
||||
1. Logos and Copyright
|
||||
You may not remove, alter, or hide any logos, trademarks, or copyright
|
||||
notices displayed in the web interface or within the source code.
|
||||
|
||||
2. Contributor Terms
|
||||
By contributing to this project, you agree that:
|
||||
a. The project maintainer may update or revise this open-source license
|
||||
to make it more permissive or more restrictive as needed.
|
||||
b. Your contributions may be used for commercial purposes, including
|
||||
but not limited to the project’s cloud and business operations.
|
||||
|
||||
All other terms of the GNU General Public License, version 3 (GPLv3) remain in effect.
|
||||
Full text: https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
Copyright (c) 2026-present FIT2CLOUD.
|
||||
21
OWNERS
Normal file
21
OWNERS
Normal file
@@ -0,0 +1,21 @@
|
||||
reviewers:
|
||||
- fit2-zhao
|
||||
- xiaomeinvG
|
||||
- song-tianyang
|
||||
- AgAngle
|
||||
- ba1q1
|
||||
- song-cc-rock
|
||||
- WangXu10
|
||||
- fit2cloudwxx
|
||||
- 1myuan
|
||||
|
||||
approvers:
|
||||
- fit2-zhao
|
||||
- xiaomeinvG
|
||||
- song-tianyang
|
||||
- AgAngle
|
||||
- ba1q1
|
||||
- song-cc-rock
|
||||
- WangXu10
|
||||
- fit2cloudwxx
|
||||
- 1myuan
|
||||
140
README.md
Normal file
140
README.md
Normal file
@@ -0,0 +1,140 @@
|
||||
<h1 align="center">Cordys CRM</h1>
|
||||
<h3 align="center">新一代的开源 AI CRM 系统</h3>
|
||||
<p align="center">
|
||||
<a href="https://trendshift.io/repositories/15469" target="_blank"><img src="https://trendshift.io/api/badge/repositories/15469" alt="1Panel-dev%2FCordysCRM | Trendshift" style="width: 240px; height: auto;" /></a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/1Panel-dev/CordysCRM/releases"><img src="https://img.shields.io/github/v/release/1Panel-dev/CordysCRM" alt="Latest release"></a>
|
||||
<a href="https://github.com/1Panel-dev/CordysCRM"><img src="https://img.shields.io/github/stars/1Panel-dev/CordysCRM?color=%231890FF&style=flat-square" alt="Stars"></a>
|
||||
<a href="https://hub.docker.com/r/1panel/cordys-crm"><img src="https://img.shields.io/docker/pulls/1panel/cordys-crm?label=downloads" alt="Download"></a><br/>
|
||||
</p>
|
||||
|
||||
<hr/>
|
||||
|
||||
## 什么是 Cordys CRM?
|
||||
|
||||
**Cordys CRM** 是新一代的开源 AI CRM 系统,是集信息化、数字化、智能化于一体的「客户关系管理系统」,由 [飞致云](https://fit2cloud.com/) 匠心出品。
|
||||
|
||||
Cordys CRM 能够帮助企业实现从线索到回款(L2C)的全流程精细化管理,覆盖线索获取、智能分配、客户与联系人管理、商机跟进、合同签约及回款执行,构建端到端的销售运营闭环。
|
||||
|
||||
<img alt="Cordys CRM Overview" src="https://cordys.cn/images/cordyscrm-202606.png" />
|
||||
|
||||
## 核心优势
|
||||
|
||||
- **灵活配置 · 高效协同**:现代化架构,操作简洁流畅;精细权限与模块化配置,无缝集成主流办公平台,赋能团队高效协同;
|
||||
- **安全自主 · 深度可控**:为私有化部署而生,数据100%自主可控;开放API与标准接口,支持深度集成与定制开发,筑牢安全与发展基石;
|
||||
- **智能 BI · 决策赋能**:深度融合 DataEase 分析引擎,销售数据可视化呈现;支持自助探索与实时洞察,驱动精准决策与业绩增长;
|
||||
- **AI 赋能 · 智能提效**:开放 CRM Skills 接口,让 AI 深入销售每个环节!OpenClaw、WorkBuddy 等智能助手 7×24 小时在线,从线索筛选到成单分析,全流程提效。
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 安装部署
|
||||
|
||||
准备一台 Linux 服务器,安装好 [Docker](https://docs.docker.com/get-docker/) 后,执行以下一键安装脚本。
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name cordys-crm \
|
||||
--restart unless-stopped \
|
||||
-p 8081:8081 \
|
||||
-p 8082:8082 \
|
||||
-v ~/cordys:/opt/cordys \
|
||||
1panel/cordys-crm
|
||||
```
|
||||
|
||||
你也可以通过 [1Panel 应用商店](https://cordys.cn/docs/installation/1panel_installtion/) 来安装部署 Cordys CRM。
|
||||
|
||||
在无法联网的环境中,还可以通过 [离线安装包](https://cordys.cn/docs/installation/offline_installtion/) 来安装部署 Cordys CRM。
|
||||
|
||||
### 访问方式
|
||||
|
||||
- 在浏览器中打开: http://<你的服务器IP>:8081/
|
||||
- 用户名: `admin`
|
||||
- 密码: `CordysCRM`
|
||||
|
||||
### 联系我们
|
||||
|
||||
安装完成后,可以参考 [在线文档](https://cordys.cn/docs/) 来使用 Cordys CRM。
|
||||
|
||||
你可以通过下方的微信交流群,与 Cordys CRM 开源项目组进行交流和反馈。
|
||||
|
||||
<image height="150px" width="150px" alt="Cordys CRM QRCode" src="https://resource.fit2cloud.com/1panel/cordys-crm/img/wechat.png?v=20250904" />
|
||||
|
||||
## UI 展示
|
||||
|
||||
<table style="border-collapse: collapse; border: 1px solid black;">
|
||||
<tr>
|
||||
<td style="padding: 5px;background-color:#fff;"><img src= "https://resource.fit2cloud.com/1panel/cordys-crm/img/setting.png" alt="Settings" /></td>
|
||||
<td style="padding: 5px;background-color:#fff;"><img src= "https://resource.fit2cloud.com/1panel/cordys-crm/img/rbac.png" alt="RBAC" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 5px;background-color:#fff;"><img src= "https://resource.fit2cloud.com/1panel/cordys-crm/img/opportunity.png" alt="Opportunity List" /></td>
|
||||
<td style="padding: 5px;background-color:#fff;"><img src= "https://resource.fit2cloud.com/1panel/cordys-crm/img/opportunity-detail.png" alt="Opportunity Detail" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 5px;background-color:#fff;"><img src= "https://resource.fit2cloud.com/1panel/cordys-crm/img/bi.png" alt="BI" /></td>
|
||||
<td style="padding: 5px;background-color:#fff;"><img src= "https://resource.fit2cloud.com/1panel/cordys-crm/img/ai.png" alt="AI" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://www.star-history.com/#1Panel-dev/CordysCRM&type=date&legend=top-left)
|
||||
|
||||
## Roadmap
|
||||
|
||||
- [x] 2026.06: Cordys CRM 下载量突破 30 万次
|
||||
- [x] 2026.05.29:[v1.7.0 新增审批流、审批记录功能](https://github.com/1Panel-dev/CordysCRM/releases/tag/v1.7.0) 发布
|
||||
- [x] 2026.03.26:[v1.6.0 新增订单模块、计算组件函数功能增强](https://github.com/1Panel-dev/CordysCRM/releases/tag/v1.6.0) 发布
|
||||
- [x] 2026.03.11:[Cordys CRM Skills 正式发布](https://github.com/1Panel-dev/CordysCRM-skills)
|
||||
- [x] 2026.01.29:[v1.5.0 新增工商抬头管理、发票管理、回款管理](https://github.com/1Panel-dev/CordysCRM/releases/tag/v1.5.0)
|
||||
- [x] 2025.12.18:[v1.4.0 新增标讯、报价和合同模块](https://github.com/1Panel-dev/CordysCRM/releases/tag/v1.4.0)
|
||||
- [x] 2025.12.12:[v1.3.6](https://github.com/1Panel-dev/CordysCRM/releases/tag/v1.3.6) 发布
|
||||
- [x] 2025.12.04:[v1.3.5](https://github.com/1Panel-dev/CordysCRM/releases/tag/v1.3.5) 发布
|
||||
- [x] 2025.11.28:[v1.3.4](https://github.com/1Panel-dev/CordysCRM/releases/tag/v1.3.4) 发布
|
||||
- [x] 2025.11.21:[v1.3.3](https://github.com/1Panel-dev/CordysCRM/releases/tag/v1.3.3) 发布
|
||||
- [x] 2025.11.14:[v1.3.2](https://github.com/1Panel-dev/CordysCRM/releases/tag/v1.3.2) 发布
|
||||
- [x] 2025.11.05:[v1.3.1](https://github.com/1Panel-dev/CordysCRM/releases/tag/v1.3.1) 发布
|
||||
- [x] 2025.11.03:[v1.3.0](https://github.com/1Panel-dev/CordysCRM/releases/tag/v1.3.0) 发布,代码正式开源
|
||||
- [x] 2025.10.17:[v1.2.3](https://github.com/1Panel-dev/CordysCRM/releases/tag/v1.2.3) 发布
|
||||
- [x] 2025.10.11:[v1.2.2](https://github.com/1Panel-dev/CordysCRM/releases/tag/v1.2.2) 发布
|
||||
- [x] 2025.09.26:[v1.2.1](https://github.com/1Panel-dev/CordysCRM/releases/tag/v1.2.1) 发布
|
||||
- [x] 2025.09.22:Cordys CRM 下载量突破 10 万次
|
||||
- [x] 2025.09.19:[v1.2.0](https://github.com/1Panel-dev/CordysCRM/releases/tag/v1.2.0) 发布,开放 MCP Server,并完成和 MaxKB 的对接
|
||||
- [x] 2025.09.12:[v1.1.9](https://github.com/1Panel-dev/CordysCRM/releases/tag/v1.1.9) 发布
|
||||
- [x] 2025.09.05:[v1.1.8](https://github.com/1Panel-dev/CordysCRM/releases/tag/v1.1.8) 发布
|
||||
- [x] 2025.09.01:[v1.1.7](https://github.com/1Panel-dev/CordysCRM/releases/tag/v1.1.7) 发布
|
||||
- [x] 2025.08.27:[v1.1.6](https://github.com/1Panel-dev/CordysCRM/releases/tag/v1.1.6) 发布
|
||||
- [x] 2025.08.27:[v1.1.5](https://github.com/1Panel-dev/CordysCRM/releases/tag/v1.1.5) 发布,开始公测
|
||||
- [x] 2025.08:完成与 SQLBot 和 DataEase 的对接
|
||||
- [x] 2025.07:吃自己的狗粮,成功替换飞致云使用 7 年的 Salesforce CRM
|
||||
- [x] 2025.06:v1.0 开发完成
|
||||
- [x] 2024.09:写下第一行代码
|
||||
|
||||
## 技术栈
|
||||
|
||||
- 后端:[Spring Boot](https://spring.io/projects/spring-boot)
|
||||
- 前端:[Vue.js](https://vuejs.org/) 、[Naive-UI](https://www.naiveui.com/) 、[Vant-UI](https://vant-ui.github.io/)
|
||||
- 中间件:[MySQL](https://www.mysql.com/) , [Redis](https://redis.com/)
|
||||
- 基础设施:[Docker](https://www.docker.com/)
|
||||
|
||||
## 飞致云旗下的其他明星项目
|
||||
|
||||
- [JumpServer](https://github.com/jumpserver/jumpserver/) - 广受欢迎的开源堡垒机
|
||||
- [1Panel](https://github.com/1panel-dev/1panel/) - 现代化、开源的 Linux 服务器运维管理面板
|
||||
- [MaxKB](https://github.com/1panel-dev/MaxKB/) - 强大易用的企业级智能体平台
|
||||
- [DataEase](https://github.com/dataease/dataease/) - 人人可用的开源 BI 工具
|
||||
- [SQLBot](https://github.com/dataease/SQLBot/) - 基于大模型和 RAG 的智能问数系统
|
||||
- [MeterSphere](https://github.com/metersphere/metersphere/) - 新一代的开源持续测试工具
|
||||
- [Halo](https://github.com/halo-dev/halo/) - 强大易用的开源建站工具
|
||||
|
||||
## License
|
||||
|
||||
本仓库遵循 [FIT2CLOUD Open Source License](LICENSE) 开源协议,该许可证本质上是 GPLv3,但有一些额外的限制。
|
||||
|
||||
你可以基于 Cordys CRM 的源代码进行二次开发,但是需要遵守以下规定:
|
||||
|
||||
- 不能替换和修改 Cordys CRM 的 Logo 和版权信息;
|
||||
- 二次开发后的衍生作品必须遵守 GPL V3 的开源义务。
|
||||
|
||||
如需商业授权,请联系:`support@fit2cloud.com`。
|
||||
17
SECURITY.md
Normal file
17
SECURITY.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# 安全说明
|
||||
|
||||
如果您发现安全问题,请直接联系我们:
|
||||
|
||||
- support@fit2cloud.com
|
||||
- 400-052-0755
|
||||
|
||||
感谢您的支持!
|
||||
|
||||
# Security Policy
|
||||
|
||||
All security bugs should be reported to the contact as below:
|
||||
|
||||
- support@fit2cloud.com
|
||||
- 400-052-0755
|
||||
|
||||
Thanks for your support!
|
||||
38
backend/.gitignore
vendored
Normal file
38
backend/.gitignore
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
.DS_Store
|
||||
node_modules
|
||||
node/
|
||||
/dist
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
*.iml
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
|
||||
src/main/resources/static
|
||||
src/main/resources/public
|
||||
|
||||
|
||||
target
|
||||
.settings
|
||||
.project
|
||||
.classpath
|
||||
.factorypath
|
||||
/crm/src/main/resources/packages/
|
||||
/app/src/main/resources/static/
|
||||
/crm/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
|
||||
123
backend/app/pom.xml
Normal file
123
backend/app/pom.xml
Normal file
@@ -0,0 +1,123 @@
|
||||
<?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>cn.cordys</groupId>
|
||||
<artifactId>backend</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>app</artifactId>
|
||||
<version>${revision}</version>
|
||||
<name>app</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.cordys</groupId>
|
||||
<artifactId>framework</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.cordys</groupId>
|
||||
<artifactId>crm</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/java</directory>
|
||||
<includes>
|
||||
<include>**/*.properties</include>
|
||||
<include>**/*.xml</include>
|
||||
<include>**/*.json</include>
|
||||
<include>**/*.tpl</include>
|
||||
<include>**/*.js</include>
|
||||
</includes>
|
||||
<filtering>false</filtering>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<includes>
|
||||
<include>**/*</include>
|
||||
</includes>
|
||||
<filtering>false</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
<!-- Spring Boot 插件,支持 Spring Boot 应用的构建 -->
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</exclude>
|
||||
</excludes>
|
||||
<loaderImplementation>CLASSIC</loaderImplementation>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Maven Clean 插件,清理资源文件夹中的静态文件 -->
|
||||
<plugin>
|
||||
<artifactId>maven-clean-plugin</artifactId>
|
||||
<configuration>
|
||||
<filesets>
|
||||
<fileset>
|
||||
<directory>src/main/resources/static</directory>
|
||||
<includes>
|
||||
<include>**</include>
|
||||
</includes>
|
||||
<followSymlinks>false</followSymlinks>
|
||||
</fileset>
|
||||
</filesets>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Maven Antrun 插件,用于复制前端资源到静态目录 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>main-class-placement</id>
|
||||
<phase>generate-resources</phase>
|
||||
<configuration>
|
||||
<skip>${skipAntRunForJenkins}</skip>
|
||||
<target>
|
||||
<!-- 复制移动端资源到静态目录 -->
|
||||
<copy todir="src/main/resources/static/mobile" failonerror="false">
|
||||
<fileset dir="../../frontend/packages/mobile/dist"/>
|
||||
</copy>
|
||||
<!-- 复制WEB端资源到静态目录 -->
|
||||
<copy todir="src/main/resources/static" failonerror="false">
|
||||
<fileset dir="../../frontend/packages/web/dist"/>
|
||||
</copy>
|
||||
</target>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>run</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- Maven Surefire 插件,配置测试执行顺序 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<runOrder>alphabetical</runOrder>
|
||||
<argLine>${argLine} -Dfile.encoding=UTF-8</argLine>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
</build>
|
||||
|
||||
</project>
|
||||
28
backend/app/src/main/java/cn/cordys/Application.java
Normal file
28
backend/app/src/main/java/cn/cordys/Application.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package cn.cordys;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
|
||||
import org.springframework.boot.web.servlet.ServletComponentScan;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
|
||||
@SpringBootApplication(exclude = {
|
||||
QuartzAutoConfiguration.class,
|
||||
LdapAutoConfiguration.class,
|
||||
Neo4jAutoConfiguration.class
|
||||
})
|
||||
@PropertySource(value = {
|
||||
"classpath:commons.properties",
|
||||
"file:/opt/cordys/conf/cordys-crm.properties",
|
||||
}, encoding = "UTF-8", ignoreResourceNotFound = true)
|
||||
@ServletComponentScan
|
||||
public class Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
108
backend/app/src/main/java/cn/cordys/listener/AppListener.java
Normal file
108
backend/app/src/main/java/cn/cordys/listener/AppListener.java
Normal file
@@ -0,0 +1,108 @@
|
||||
package cn.cordys.listener;
|
||||
|
||||
import cn.cordys.common.service.DataInitService;
|
||||
import cn.cordys.common.uid.impl.DefaultUidGenerator;
|
||||
import cn.cordys.common.util.HikariCPUtils;
|
||||
import cn.cordys.common.util.JSON;
|
||||
import cn.cordys.common.util.rsa.RsaKey;
|
||||
import cn.cordys.common.util.rsa.RsaUtils;
|
||||
import cn.cordys.crm.system.service.ExportTaskStopService;
|
||||
import cn.cordys.crm.system.service.ExtScheduleService;
|
||||
import cn.cordys.crm.system.service.SystemService;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
class AppListener implements ApplicationRunner {
|
||||
@Resource
|
||||
private DefaultUidGenerator uidGenerator;
|
||||
|
||||
@Resource
|
||||
private ExtScheduleService extScheduleService;
|
||||
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
@Resource
|
||||
private DataInitService dataInitService;
|
||||
|
||||
@Resource
|
||||
private ExportTaskStopService exportTaskStopService;
|
||||
@Resource
|
||||
private SystemService systemService;
|
||||
|
||||
/**
|
||||
* 应用启动后执行的初始化方法。
|
||||
* <p>
|
||||
* 此方法会依次初始化唯一 ID 生成器、MinIO 配置和 RSA 配置。
|
||||
* </p>
|
||||
*
|
||||
* @param args 启动参数
|
||||
*/
|
||||
@Override
|
||||
public void run(ApplicationArguments args) {
|
||||
log.info("===== 开始初始化配置 =====");
|
||||
|
||||
// 初始化唯一ID生成器
|
||||
uidGenerator.init();
|
||||
|
||||
// 初始化RSA配置
|
||||
log.info("初始化RSA配置");
|
||||
initializeRsaConfiguration();
|
||||
|
||||
log.info("初始化定时任务");
|
||||
extScheduleService.startEnableSchedules();
|
||||
|
||||
HikariCPUtils.printHikariCPStatus();
|
||||
|
||||
log.info("初始化默认组织数据");
|
||||
dataInitService.initOneTime();
|
||||
|
||||
log.info("停止导出任务");
|
||||
exportTaskStopService.stopPreparedAll();
|
||||
|
||||
log.info("清理表单缓存");
|
||||
systemService.clearFormCache();
|
||||
|
||||
log.info("===== 完成初始化配置 =====");
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 RSA 配置。
|
||||
* <p>
|
||||
* 此方法首先尝试加载现有的 RSA 密钥。如果不存在,则生成新的 RSA 密钥并保存到文件系统。
|
||||
* </p>
|
||||
*/
|
||||
private void initializeRsaConfiguration() {
|
||||
String redisKey = "rsa:key";
|
||||
try {
|
||||
// 从 Redis 获取 RSA 密钥
|
||||
String rsaStr = stringRedisTemplate.opsForValue().get(redisKey);
|
||||
if (StringUtils.isNotBlank(rsaStr)) {
|
||||
// 如果 RSA 密钥存在,反序列化并设置密钥
|
||||
RsaKey rsaKey = JSON.parseObject(rsaStr, RsaKey.class);
|
||||
RsaUtils.setRsaKey(rsaKey);
|
||||
return;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("从 Redis 获取 RSA 配置失败", e);
|
||||
}
|
||||
|
||||
try {
|
||||
// 如果 Redis 中没有密钥,生成新的 RSA 密钥并保存到 Redis
|
||||
RsaKey rsaKey = RsaUtils.getRsaKey();
|
||||
stringRedisTemplate.opsForValue().set(redisKey, JSON.toJSONString(rsaKey));
|
||||
RsaUtils.setRsaKey(rsaKey);
|
||||
} catch (Exception e) {
|
||||
log.error("初始化 RSA 配置失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package cn.cordys.listener;
|
||||
|
||||
import org.springframework.boot.web.servlet.error.ErrorController;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
/**
|
||||
* 自定义错误控制器类,用于处理应用中的错误页面请求。
|
||||
* <p>
|
||||
* 该控制器会将所有错误页面的请求重定向到网站的根页面("/")。
|
||||
* </p>
|
||||
*/
|
||||
@Controller
|
||||
public class CustomError implements ErrorController {
|
||||
|
||||
/**
|
||||
* 错误处理方法,当发生错误时,会将请求重定向到根页面。
|
||||
*
|
||||
* @return 重定向到根页面
|
||||
*/
|
||||
@GetMapping("/error")
|
||||
public String redirectRoot() {
|
||||
return "redirect:/";
|
||||
}
|
||||
}
|
||||
44
backend/app/src/main/java/cn/cordys/listener/Index.java
Normal file
44
backend/app/src/main/java/cn/cordys/listener/Index.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package cn.cordys.listener;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
/**
|
||||
* 主页控制器类,处理访问根页面("/")和登录页面("/login")的请求。
|
||||
* <p>
|
||||
* 该控制器负责将请求转发到 `index.html` 页面。
|
||||
* </p>
|
||||
*/
|
||||
@Controller
|
||||
public class Index {
|
||||
/**
|
||||
* 处理根路径("/")的请求,并返回首页 `index.html` 页面。
|
||||
*
|
||||
* @return 返回首页的视图名称
|
||||
*/
|
||||
@GetMapping("/web")
|
||||
public String index() {
|
||||
return "index.html";
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理移动端根路径("/")的请求,并返回首页 `/mobile/index.html` 页面。
|
||||
*
|
||||
* @return 返回首页的视图名称
|
||||
*/
|
||||
@GetMapping("/mobile")
|
||||
public String mobileIndex() {
|
||||
return "mobile/index.html";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 处理登录页面("/login")的请求,并返回 `index.html` 页面。
|
||||
*
|
||||
* @return 返回登录页面的视图名称
|
||||
*/
|
||||
@GetMapping(value = "/login")
|
||||
public String login() {
|
||||
return "/index.html";
|
||||
}
|
||||
}
|
||||
7
backend/app/src/main/resources/banner.txt
Normal file
7
backend/app/src/main/resources/banner.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
██████╗ ██████╗ ██████╗ ██████╗ ██╗ ██╗███████╗ ██████╗██████╗ ███╗ ███╗
|
||||
██╔════╝██╔═══██╗██╔══██╗██╔══██╗╚██╗ ██╔╝██╔════╝ ██╔════╝██╔══██╗████╗ ████║
|
||||
██║ ██║ ██║██████╔╝██║ ██║ ╚████╔╝ ███████╗ ██║ ██████╔╝██╔████╔██║
|
||||
██║ ██║ ██║██╔══██╗██║ ██║ ╚██╔╝ ╚════██║ ██║ ██╔══██╗██║╚██╔╝██║
|
||||
╚██████╗╚██████╔╝██║ ██║██████╔╝ ██║ ███████║ ╚██████╗██║ ██║██║ ╚═╝ ██║
|
||||
╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚═╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝
|
||||
93
backend/app/src/main/resources/commons.properties
Normal file
93
backend/app/src/main/resources/commons.properties
Normal file
@@ -0,0 +1,93 @@
|
||||
# Application Settings
|
||||
spring.application.name=cordys-crm
|
||||
server.port=8081
|
||||
|
||||
# Compression Settings (gzip)
|
||||
server.compression.enabled=true
|
||||
server.compression.mime-types=application/json,application/xml,text/html,text/xml,text/plain,application/javascript,text/css,text/javascript,image/jpeg
|
||||
server.compression.min-response-size=2048
|
||||
|
||||
# Logging Settings
|
||||
logging.file.path=/opt/cordys/logs/cordys-crm
|
||||
|
||||
# DataSource Configuration (HikariCP)
|
||||
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
|
||||
spring.datasource.hikari.maximum-pool-size=100
|
||||
spring.datasource.hikari.minimum-idle=10
|
||||
spring.datasource.hikari.idle-timeout=300000
|
||||
spring.datasource.hikari.auto-commit=true
|
||||
spring.datasource.hikari.pool-name=DatebookHikariCP
|
||||
spring.datasource.hikari.max-lifetime=1800000
|
||||
spring.datasource.hikari.connection-timeout=30000
|
||||
spring.datasource.hikari.connection-test-query=SELECT 1
|
||||
|
||||
# Quartz Scheduler DataSource Settings
|
||||
quartz.enabled=true
|
||||
quartz.scheduler-name=cordys-crm-quartz
|
||||
quartz.thread-count=10
|
||||
quartz.properties.org.quartz.jobStore.acquireTriggersWithinLock=true
|
||||
|
||||
spring.datasource.quartz.url=${spring.datasource.url}
|
||||
spring.datasource.quartz.username=${spring.datasource.username}
|
||||
spring.datasource.quartz.password=${spring.datasource.password}
|
||||
spring.datasource.quartz.hikari.maximum-pool-size=50
|
||||
spring.datasource.quartz.hikari.minimum-idle=10
|
||||
spring.datasource.quartz.hikari.idle-timeout=300000
|
||||
spring.datasource.quartz.hikari.auto-commit=true
|
||||
spring.datasource.quartz.hikari.pool-name=DatebookHikariCP
|
||||
spring.datasource.quartz.hikari.max-lifetime=1800000
|
||||
spring.datasource.quartz.hikari.connection-timeout=30000
|
||||
spring.datasource.quartz.hikari.connection-test-query=SELECT 1
|
||||
|
||||
# MyBatis Configuration
|
||||
mybatis.configuration.cache-enabled=false
|
||||
mybatis.configuration.lazy-loading-enabled=false
|
||||
mybatis.configuration.aggressive-lazy-loading=true
|
||||
mybatis.configuration.use-column-label=true
|
||||
mybatis.configuration.auto-mapping-behavior=full
|
||||
mybatis.configuration.default-statement-timeout=25000
|
||||
mybatis.configuration.map-underscore-to-camel-case=true
|
||||
|
||||
# Virtual Thread Settings (for Thread Management)
|
||||
spring.threads.virtual.enabled=true
|
||||
spring.mvc.log-request-details=false
|
||||
|
||||
# Flyway Database Migration Configuration
|
||||
spring.flyway.enabled=true
|
||||
spring.flyway.baseline-on-migrate=true
|
||||
spring.flyway.locations=classpath:migration
|
||||
spring.flyway.table=cordys_crm_version
|
||||
spring.flyway.baseline-version=0
|
||||
spring.flyway.encoding=UTF-8
|
||||
spring.flyway.validate-on-migrate=false
|
||||
|
||||
# File Upload Configuration
|
||||
spring.servlet.multipart.max-file-size=1024MB
|
||||
spring.servlet.multipart.max-request-size=1024MB
|
||||
|
||||
# Redisson (Session Management with Redis)
|
||||
spring.session.timeout=43200s
|
||||
spring.session.redis.repository-type=indexed
|
||||
spring.cache.type=redis
|
||||
|
||||
#spring.redis.redisson.file=file:/opt/cordys/conf/redisson.yml
|
||||
|
||||
# Template Engines (Freemarker, Groovy)
|
||||
spring.freemarker.check-template-location=false
|
||||
spring.groovy.template.check-template-location=false
|
||||
|
||||
# Swagger Configuration (API Documentation)
|
||||
springdoc.swagger-ui.enabled=true
|
||||
springdoc.api-docs.enabled=true
|
||||
springdoc.api-docs.groups.enabled=true
|
||||
|
||||
# i18n
|
||||
spring.messages.basename=i18n/cordys-crm
|
||||
|
||||
# Enable whitelist functionality, if not enabled, access will not be restricted.
|
||||
allowed.ip.ranges.enabled=false
|
||||
# Enable whitelist functionality, if not enabled, access will not be restricted.
|
||||
allowed.ip.ranges=
|
||||
|
||||
# List of URLs that require XSS filtering, supports Ant-style path matching, e.g., /api/**. If no URLs need to be filtered, it can be left empty.
|
||||
# xss.protection.url.list=/account/follow/**,/announcement/add
|
||||
185
backend/app/src/main/resources/logback-spring.xml
Normal file
185
backend/app/src/main/resources/logback-spring.xml
Normal file
@@ -0,0 +1,185 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration debug="true">
|
||||
|
||||
<property resource="commons.properties"/>
|
||||
<property file="/opt/cordys/conf/cordys-crm.properties" ignoreResourceNotFound="true"/>
|
||||
|
||||
<!-- Console 输出 -->
|
||||
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d %5p %40.40c:%4L - %m%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- ===================== 文件 Appender ===================== -->
|
||||
|
||||
<appender name="traceAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>TRACE</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
<File>${logging.file.path}/trace.log</File>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<FileNamePattern>${logging.file.path}/history/trace.%d{yyyyMMdd}-%i.log</FileNamePattern>
|
||||
<maxHistory>${logger.max.history:-30}</maxHistory>
|
||||
<maxFileSize>50MB</maxFileSize>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<charset>UTF-8</charset>
|
||||
<Pattern>%d [%thread] %-5level %logger{36} %line - %msg%n</Pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="debugAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>DEBUG</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
<File>${logging.file.path}/debug.log</File>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<FileNamePattern>${logging.file.path}/history/debug.%d{yyyyMMdd}-%i.log</FileNamePattern>
|
||||
<maxHistory>${logger.max.history:-30}</maxHistory>
|
||||
<maxFileSize>50MB</maxFileSize>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<charset>UTF-8</charset>
|
||||
<Pattern>%d [%thread] %-5level %logger{36} %line - %msg%n</Pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="infoAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>INFO</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
<File>${logging.file.path}/info.log</File>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<FileNamePattern>${logging.file.path}/history/info.%d{yyyyMMdd}-%i.log</FileNamePattern>
|
||||
<maxHistory>${logger.max.history:-30}</maxHistory>
|
||||
<maxFileSize>50MB</maxFileSize>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<charset>UTF-8</charset>
|
||||
<Pattern>%d [%thread] %-5level %logger{36} %line - %msg%n</Pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="warnAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>WARN</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
<File>${logging.file.path}/warn.log</File>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<FileNamePattern>${logging.file.path}/history/warn.%d{yyyyMMdd}-%i.log</FileNamePattern>
|
||||
<maxHistory>${logger.max.history:-30}</maxHistory>
|
||||
<maxFileSize>50MB</maxFileSize>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<charset>UTF-8</charset>
|
||||
<Pattern>%d [%thread] %-5level %logger{36} %line - %msg%n</Pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="errorAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>ERROR</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
<File>${logging.file.path}/error.log</File>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<FileNamePattern>${logging.file.path}/history/error.%d{yyyyMMdd}-%i.log</FileNamePattern>
|
||||
<maxHistory>${logger.max.history:-30}</maxHistory>
|
||||
<maxFileSize>50MB</maxFileSize>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<charset>UTF-8</charset>
|
||||
<Pattern>%d [%thread] %-5level %logger{36} %line - %msg%n</Pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- ===================== Async Appender ===================== -->
|
||||
|
||||
<appender name="traceAsyncAppender" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>TRACE</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
<queueSize>10000</queueSize>
|
||||
<appender-ref ref="traceAppender"/>
|
||||
</appender>
|
||||
|
||||
<appender name="debugAsyncAppender" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>DEBUG</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
<queueSize>10000</queueSize>
|
||||
<appender-ref ref="debugAppender"/>
|
||||
</appender>
|
||||
|
||||
<appender name="infoAsyncAppender" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>INFO</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
<queueSize>10000</queueSize>
|
||||
<appender-ref ref="infoAppender"/>
|
||||
</appender>
|
||||
|
||||
<appender name="warnAsyncAppender" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>WARN</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
<queueSize>10000</queueSize>
|
||||
<includeCallerData>true</includeCallerData>
|
||||
<appender-ref ref="warnAppender"/>
|
||||
</appender>
|
||||
|
||||
<appender name="errorAsyncAppender" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>ERROR</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
<queueSize>10000</queueSize>
|
||||
<includeCallerData>true</includeCallerData>
|
||||
<appender-ref ref="errorAppender"/>
|
||||
</appender>
|
||||
|
||||
<!-- ===================== Logger ===================== -->
|
||||
|
||||
<!-- cn.cordys 模块日志 -->
|
||||
<logger name="cn.cordys" additivity="false" level="${logback.level:INFO}">
|
||||
<appender-ref ref="traceAsyncAppender"/>
|
||||
<appender-ref ref="debugAsyncAppender"/>
|
||||
<appender-ref ref="infoAsyncAppender"/>
|
||||
<appender-ref ref="warnAsyncAppender"/>
|
||||
<appender-ref ref="errorAsyncAppender"/>
|
||||
<appender-ref ref="console"/>
|
||||
</logger>
|
||||
|
||||
<!-- cn.cordys.Application 单独 INFO 输出 -->
|
||||
<logger name="cn.cordys.Application" additivity="false" level="${logback.level:INFO}">
|
||||
<appender-ref ref="infoAsyncAppender"/>
|
||||
</logger>
|
||||
|
||||
<!-- 容器日志 -->
|
||||
<logger name="org.eclipse.jetty.ee10.servlet.ServletChannel" level="ERROR"/>
|
||||
|
||||
<!-- ===================== Root ===================== -->
|
||||
<root level="INFO">
|
||||
<appender-ref ref="console"/>
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
34
backend/crm/pom.xml
Normal file
34
backend/crm/pom.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<?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>cn.cordys</groupId>
|
||||
<artifactId>backend</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>crm</artifactId>
|
||||
<version>${revision}</version>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.cordys</groupId>
|
||||
<artifactId>framework</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>21</source>
|
||||
<target>21</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,401 @@
|
||||
package cn.cordys.common.constants;
|
||||
|
||||
import cn.cordys.crm.system.dto.field.base.BaseField;
|
||||
import cn.cordys.crm.system.dto.field.base.SubField;
|
||||
import lombok.Getter;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 业务模块字段(定义在主表中,有特定业务含义)(标准字段)
|
||||
*
|
||||
* @Author: jianxing
|
||||
* @CreateTime: 2025-02-18 17:27
|
||||
*/
|
||||
@Getter
|
||||
public enum BusinessModuleField {
|
||||
|
||||
/*------ start: CUSTOMER ------*/
|
||||
/**
|
||||
* 客户名称
|
||||
*/
|
||||
CUSTOMER_NAME("customerName", "name", Set.of("rules.required", "mobile", "readable"), FormKey.CUSTOMER.getKey()),
|
||||
/**
|
||||
* 负责人
|
||||
*/
|
||||
CUSTOMER_OWNER("customerOwner", "owner", Set.of("rules.required", "mobile", "readable"), FormKey.CUSTOMER.getKey()),
|
||||
/*------ end: CUSTOMER ------*/
|
||||
|
||||
/*------ start: CLUE ------*/
|
||||
/**
|
||||
* 线索名称
|
||||
*/
|
||||
CLUE_NAME("clueName", "name", Set.of("rules.required", "mobile", "readable"), FormKey.CLUE.getKey()),
|
||||
/**
|
||||
* 负责人
|
||||
*/
|
||||
CLUE_OWNER("clueOwner", "owner", Set.of("rules.required", "mobile", "readable"), FormKey.CLUE.getKey()),
|
||||
/**
|
||||
* 联系人
|
||||
*/
|
||||
CLUE_CONTACT("clueContactName", "contact", Set.of(), FormKey.CLUE.getKey()),
|
||||
/**
|
||||
* 联系人电话
|
||||
*/
|
||||
CLUE_CONTACT_PHONE("clueContactPhone", "phone", Set.of(), FormKey.CLUE.getKey()),
|
||||
/**
|
||||
* 意向产品
|
||||
*/
|
||||
CLUE_PRODUCTS("clueProduct", "products", Set.of(), FormKey.CLUE.getKey()),
|
||||
/*------ end: CUSTOMER ------*/
|
||||
|
||||
/*------ start: CUSTOMER_MANAGEMENT_CONTACT ------*/
|
||||
/**
|
||||
* 联系人客户id
|
||||
*/
|
||||
CUSTOMER_CONTACT_CUSTOMER("contactCustomer", "customerId", Set.of(), FormKey.CONTACT.getKey()),
|
||||
/**
|
||||
* 联系人责任人
|
||||
*/
|
||||
CUSTOMER_CONTACT_OWNER("contactOwner", "owner", Set.of("rules.required", "mobile", "readable"), FormKey.CONTACT.getKey()),
|
||||
/**
|
||||
* 联系人名称
|
||||
*/
|
||||
CUSTOMER_CONTACT_NAME("contactName", "name", Set.of("rules.required", "mobile", "readable"), FormKey.CONTACT.getKey()),
|
||||
/**
|
||||
* 联系人电话
|
||||
*/
|
||||
CUSTOMER_CONTACT_PHONE("contactPhone", "phone", Set.of(), FormKey.CONTACT.getKey()),
|
||||
/*------ end: CUSTOMER_MANAGEMENT_CONTACT ------*/
|
||||
|
||||
|
||||
/*------ start: OPPORTUNITY ------*/
|
||||
/**
|
||||
* 商机名称
|
||||
*/
|
||||
OPPORTUNITY_NAME("opportunityName", "name", Set.of("rules.required", "mobile", "readable"), FormKey.OPPORTUNITY.getKey()),
|
||||
/**
|
||||
* 客户名称
|
||||
*/
|
||||
OPPORTUNITY_CUSTOMER_NAME("opportunityCustomer", "customerId", Set.of(), FormKey.OPPORTUNITY.getKey()),
|
||||
/**
|
||||
* 商机金额
|
||||
*/
|
||||
OPPORTUNITY_AMOUNT("opportunityPrice", "amount", Set.of(), FormKey.OPPORTUNITY.getKey()),
|
||||
/**
|
||||
* 可能性
|
||||
*/
|
||||
OPPORTUNITY_POSSIBLE("opportunityWinRate", "possible", Set.of(), FormKey.OPPORTUNITY.getKey()),
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
OPPORTUNITY_END_TIME("opportunityEndTime", "expectedEndTime", Set.of(), FormKey.OPPORTUNITY.getKey()),
|
||||
/**
|
||||
* 意向产品
|
||||
*/
|
||||
OPPORTUNITY_PRODUCTS("opportunityProduct", "products", Set.of(), FormKey.OPPORTUNITY.getKey()),
|
||||
/**
|
||||
* 联系人
|
||||
*/
|
||||
OPPORTUNITY_CONTACT("opportunityContact", "contactId", Set.of(), FormKey.OPPORTUNITY.getKey()),
|
||||
|
||||
/**
|
||||
* 负责人
|
||||
*/
|
||||
OPPORTUNITY_OWNER("opportunityOwner", "owner", Set.of("rules.required", "mobile", "readable"), FormKey.OPPORTUNITY.getKey()),
|
||||
/*------ end: OPPORTUNITY ------*/
|
||||
|
||||
|
||||
|
||||
/*------ start: FOLLOW_UP_RECORD ------*/
|
||||
/**
|
||||
* 跟进类型
|
||||
*/
|
||||
FOLLOW_RECORD_TYPE("recordType", "type", Set.of("options", "rules.required", "mobile", "readable"), FormKey.FOLLOW_RECORD.getKey()),
|
||||
/**
|
||||
* 客户id
|
||||
*/
|
||||
FOLLOW_RECORD_CUSTOMER("recordCustomer", "customerId", Set.of("rules.required", "mobile", "readable"), FormKey.FOLLOW_RECORD.getKey()),
|
||||
/**
|
||||
* 商机id
|
||||
*/
|
||||
FOLLOW_RECORD_OPPORTUNITY("recordOpportunity", "opportunityId", Set.of(), FormKey.FOLLOW_RECORD.getKey()),
|
||||
/**
|
||||
* 线索id
|
||||
*/
|
||||
FOLLOW_RECORD_CLUE("recordClue", "clueId", Set.of("rules.required", "mobile", "readable"), FormKey.FOLLOW_RECORD.getKey()),
|
||||
/**
|
||||
* 责任人id
|
||||
*/
|
||||
FOLLOW_RECORD_OWNER("recordOwner", "owner", Set.of("rules.required", "mobile", "readable"), FormKey.FOLLOW_RECORD.getKey()),
|
||||
/**
|
||||
* 联系人id
|
||||
*/
|
||||
FOLLOW_RECORD_CONTACT("recordContact", "contactId", Set.of(), FormKey.FOLLOW_RECORD.getKey()),
|
||||
/**
|
||||
* 跟进内容
|
||||
*/
|
||||
FOLLOW_RECORD_CONTENT("recordDescription", "content", Set.of(), FormKey.FOLLOW_RECORD.getKey()),
|
||||
/**
|
||||
* 跟进时间
|
||||
*/
|
||||
FOLLOW_RECORD_TIME("recordTime", "followTime", Set.of(), FormKey.FOLLOW_RECORD.getKey()),
|
||||
/**
|
||||
* 跟进方式
|
||||
*/
|
||||
FOLLOW_METHOD("recordMethod", "followMethod", Set.of(), FormKey.FOLLOW_RECORD.getKey()),
|
||||
/*------ end: FOLLOW_UP_RECORD ------*/
|
||||
|
||||
|
||||
/*------ start: FOLLOW_UP_PLAN ------*/
|
||||
/**
|
||||
* 跟进类型
|
||||
*/
|
||||
FOLLOW_PLAN_TYPE("planType", "type", Set.of("options", "rules.required", "mobile", "readable"), FormKey.FOLLOW_PLAN.getKey()),
|
||||
/**
|
||||
* 客户id
|
||||
*/
|
||||
FOLLOW_PLAN_CUSTOMER("planCustomer", "customerId", Set.of("rules.required", "mobile", "readable"), FormKey.FOLLOW_PLAN.getKey()),
|
||||
/**
|
||||
* 商机id
|
||||
*/
|
||||
FOLLOW_PLAN_OPPORTUNITY("planOpportunity", "opportunityId", Set.of(), FormKey.FOLLOW_PLAN.getKey()),
|
||||
/**
|
||||
* 线索id
|
||||
*/
|
||||
FOLLOW_PLAN_CLUE("planClue", "clueId", Set.of("rules.required", "mobile", "readable"), FormKey.FOLLOW_PLAN.getKey()),
|
||||
/**
|
||||
* 责任人id
|
||||
*/
|
||||
FOLLOW_PLAN_OWNER("planOwner", "owner", Set.of("rules.required", "mobile", "readable"), FormKey.FOLLOW_PLAN.getKey()),
|
||||
/**
|
||||
* 联系人id
|
||||
*/
|
||||
FOLLOW_PLAN_CONTACT("planContact", "contactId", Set.of(), FormKey.FOLLOW_PLAN.getKey()),
|
||||
/**
|
||||
* 预计开始时间
|
||||
*/
|
||||
FOLLOW_PLAN_ESTIMATED_TIME("planStartTime", "estimatedTime", Set.of(), FormKey.FOLLOW_PLAN.getKey()),
|
||||
/**
|
||||
* 预计沟通内容
|
||||
*/
|
||||
FOLLOW_PLAN_CONTENT("planContent", "content", Set.of(), FormKey.FOLLOW_PLAN.getKey()),
|
||||
/**
|
||||
* 跟进方式
|
||||
*/
|
||||
FOLLOW_PLAN_METHOD("planMethod", "method", Set.of(), FormKey.FOLLOW_PLAN.getKey()),
|
||||
|
||||
|
||||
/*------ end: FOLLOW_UP_PLAN ------*/
|
||||
|
||||
/*------ start: PRODUCT ------*/
|
||||
PRODUCT_NAME("productName", "name", Set.of("rules.required", "mobile", "readable"), FormKey.PRODUCT.getKey()),
|
||||
PRODUCT_PRICE("productPrice", "price", Set.of(), FormKey.PRODUCT.getKey()),
|
||||
PRODUCT_STATUS("productStatus", "status", Set.of("rules.required", "mobile", "readable"), FormKey.PRODUCT.getKey()),
|
||||
/*------ end: PRODUCT ------*/
|
||||
|
||||
/**
|
||||
* 价格表单 (修改价格子表格为自定义时, 注意处理对应详情解析逻辑)
|
||||
*/
|
||||
PRICE_NAME("priceName", "name", Set.of("rules.required", "mobile", "readable"), FormKey.PRICE.getKey()),
|
||||
PRICE_STATUS("priceStatus", "status", Set.of("rules.required", "mobile", "readable"), FormKey.PRICE.getKey()),
|
||||
PRICE_PRODUCT_TABLE("priceProducts", "products", Set.of("mobile", "readable"), FormKey.PRICE.getKey()),
|
||||
PRICE_PRODUCT("priceProduct", "product", Set.of("rules.required", "mobile", "dataSourceType", "readable"), FormKey.PRICE.getKey()),
|
||||
PRICE_PRODUCT_AMOUNT("priceProductAmount", "amount", Set.of("rules.required", "mobile", "readable"), FormKey.PRICE.getKey()),
|
||||
|
||||
/**
|
||||
* 报价单表单
|
||||
*/
|
||||
QUOTATION_NAME("quotationName", "name", Set.of("rules.required", "mobile", "readable"), FormKey.QUOTATION.getKey()),
|
||||
QUOTATION_OPPORTUNITY("quotationOpportunity", "opportunityId", Set.of("rules.required", "mobile", "dataSourceType", "readable"), FormKey.QUOTATION.getKey()),
|
||||
QUOTATION_UNTIL_TIME("quotationUntilTime", "untilTime", Set.of("rules.required", "mobile", "readable"), FormKey.QUOTATION.getKey()),
|
||||
QUOTATION_TOTAL_AMOUNT("quotationTotalAmount", "amount", Set.of("rules.required", "mobile", "readable"), FormKey.QUOTATION.getKey()),
|
||||
|
||||
|
||||
/*------ start: CONTRACT_PAYMENT_PLAN ------*/
|
||||
/**
|
||||
* 负责人
|
||||
*/
|
||||
CONTRACT_PAYMENT_PLAN_OWNER("contractPaymentPlanOwner", "owner", Set.of("rules.required", "mobile", "readable"), FormKey.CONTRACT_PAYMENT_PLAN.getKey()),
|
||||
/**
|
||||
* 合同
|
||||
*/
|
||||
CONTRACT_PAYMENT_PLAN_CONTRACT("contractPaymentPlanContract", "contractId", Set.of("rules.required", "dataSourceType", "mobile", "readable"), FormKey.CONTRACT_PAYMENT_PLAN.getKey()),
|
||||
/**
|
||||
* 计划回款金额
|
||||
*/
|
||||
CONTRACT_PAYMENT_PLAN_PLAN_AMOUNT("contractPaymentPlanPlanAmount", "planAmount", Set.of("rules.required", "mobile", "readable"), FormKey.CONTRACT_PAYMENT_PLAN.getKey()),
|
||||
/**
|
||||
* 计划回款时间
|
||||
*/
|
||||
CONTRACT_PAYMENT_PLAN_PLAN_END_TIME("contractPaymentPlanPlanEndTime", "planEndTime", Set.of("rules.required", "mobile", "readable"), FormKey.CONTRACT_PAYMENT_PLAN.getKey()),
|
||||
/**
|
||||
* 回款计划名称
|
||||
*/
|
||||
CONTRACT_PAYMENT_PLAN_NAME("contractPaymentPlanName", "name", Set.of("rules.required", "mobile", "readable"), FormKey.CONTRACT_PAYMENT_PLAN.getKey()),
|
||||
/*------ end: CONTRACT_PAYMENT_PLAN ------*/
|
||||
|
||||
|
||||
/*------ start: CONTRACT ------*/
|
||||
/**
|
||||
* 合同名稱
|
||||
*/
|
||||
CONTRACT_NAME("contractName", "name", Set.of("rules.required", "mobile", "readable"), FormKey.CONTRACT.getKey()),
|
||||
|
||||
CONTRACT_CUSTOMER_NAME("contractCustomer", "customerId", Set.of("rules.required", "mobile", "readable", "dataSourceType"), FormKey.CONTRACT.getKey()),
|
||||
CONTRACT_OWNER("contractOwner", "owner", Set.of("rules.required", "mobile", "readable"), FormKey.CONTRACT.getKey()),
|
||||
CONTRACT_NO("contractNo", "number", Set.of("rules.required"), FormKey.CONTRACT.getKey()),
|
||||
CONTRACT_START_TIME("contractStartTime", "startTime", Set.of("mobile", "readable"), FormKey.CONTRACT.getKey()),
|
||||
CONTRACT_END_TIME("contractEndTime", "endTime", Set.of("mobile", "readable"), FormKey.CONTRACT.getKey()),
|
||||
CONTRACT_TOTAL_AMOUNT("contractTotalAmount", "amount", Set.of("rules.required", "mobile", "readable"), FormKey.CONTRACT.getKey()),
|
||||
|
||||
/*------ end: CONTRACT ------*/
|
||||
|
||||
/**
|
||||
* 发票
|
||||
*/
|
||||
/*------ start: CONTRACT_INVOICE ------*/
|
||||
INVOICE_NAME("invoiceName", "name", Set.of("rules.required", "mobile", "readable"), FormKey.INVOICE.getKey()),
|
||||
INVOICE_OWNER("invoiceOwner", "owner", Set.of("rules.required", "mobile", "readable"), FormKey.INVOICE.getKey()),
|
||||
INVOICE_AMOUNT("invoiceAmount", "amount", Set.of("rules.required", "readable", "mobile"), FormKey.INVOICE.getKey()),
|
||||
INVOICE_CONTRACT_ID("invoiceContract", "contractId", Set.of("rules.required", "mobile", "readable", "dataSourceType"), FormKey.INVOICE.getKey()),
|
||||
INVOICE_INVOICE_TYPE("invoiceType", "invoiceType", Set.of("rules.required", "readable", "mobile"), FormKey.INVOICE.getKey()),
|
||||
INVOICE_TAX_RATE("invoiceTaxRate", "taxRate", Set.of("rules.required", "readable", "mobile"), FormKey.INVOICE.getKey()),
|
||||
INVOICE_BUSINESS_TITLE_ID("invoiceBusinessTitle", "businessTitleId", Set.of("rules.required", "mobile", "readable", "dataSourceType"), FormKey.INVOICE.getKey()),
|
||||
/*------ end: CONTRACT_INVOICE ------*/
|
||||
|
||||
|
||||
/*------ start: CONTRACT_PAYMENT_RECORD 合同回款记录 ------*/
|
||||
CONTRACT_PAYMENT_RECORD_NO("contractPaymentRecordNo", "no", Set.of("rules.required"), FormKey.CONTRACT_PAYMENT_RECORD.getKey()),
|
||||
CONTRACT_PAYMENT_RECORD_NAME("contractPaymentRecordName", "name", Set.of("rules.required", "mobile", "readable"), FormKey.CONTRACT_PAYMENT_RECORD.getKey()),
|
||||
CONTRACT_PAYMENT_RECORD_OWNER("contractPaymentRecordOwner", "owner", Set.of("rules.required", "mobile", "readable"), FormKey.CONTRACT_PAYMENT_RECORD.getKey()),
|
||||
CONTRACT_PAYMENT_RECORD_CONTRACT("contractPaymentRecordContract", "contractId", Set.of("rules.required", "dataSourceType", "mobile", "readable"), FormKey.CONTRACT_PAYMENT_RECORD.getKey()),
|
||||
CONTRACT_PAYMENT_RECORD_PLAN("contractPaymentRecordPlan", "paymentPlanId", Set.of("dataSourceType", "mobile", "readable"), FormKey.CONTRACT_PAYMENT_RECORD.getKey()),
|
||||
CONTRACT_PAYMENT_RECORD_AMOUNT("contractPaymentRecordAmount", "recordAmount", Set.of("rules.required", "mobile", "readable"), FormKey.CONTRACT_PAYMENT_RECORD.getKey()),
|
||||
CONTRACT_PAYMENT_RECORD_END_TIME("contractPaymentRecordEndTime", "recordEndTime", Set.of("rules.required", "mobile", "readable"), FormKey.CONTRACT_PAYMENT_RECORD.getKey()),
|
||||
/*------ end: CONTRACT_PAYMENT_RECORD 合同回款记录 ------*/
|
||||
|
||||
/*------ start: ORDER ------*/
|
||||
ORDER_NAME("orderName", "name", Set.of("rules.required", "mobile", "readable"), FormKey.ORDER.getKey()),
|
||||
ORDER_CUSTOMER("orderCustomer", "customerId", Set.of("dataSourceType"), FormKey.ORDER.getKey()),
|
||||
ORDER_CONTRACT("orderContract", "contractId", Set.of("dataSourceType"), FormKey.ORDER.getKey()),
|
||||
ORDER_OWNER("orderOwner", "owner", Set.of(), FormKey.ORDER.getKey()),
|
||||
ORDER_NO("orderNo", "number", Set.of("rules.required"), FormKey.ORDER.getKey()),
|
||||
ORDER_TOTAL_AMOUNT("orderAmount", "amount", Set.of("rules.required", "mobile", "readable"), FormKey.ORDER.getKey()),
|
||||
/*------ end: ORDER ------*/
|
||||
|
||||
|
||||
/*------ start: ORDER ------*/
|
||||
CUSTOM_FORM_DATA_NAME("customFormDataName", "name", Set.of("rules.required", "mobile", "readable"), null),
|
||||
CUSTOM_FORM_DATA_OWNER("customFormDataNOwner", "owner", Set.of("rules.required", "mobile", "readable"), null),
|
||||
/*------ end: ORDER ------*/
|
||||
|
||||
;
|
||||
|
||||
/**
|
||||
* 业务字段缓存
|
||||
*/
|
||||
private static final Map<String, BusinessModuleField> INTERNAL_CACHE = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (BusinessModuleField field : values()) {
|
||||
// 防止ofKey方法频繁调用
|
||||
INTERNAL_CACHE.put(field.key, field);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段 key,field.json 中的 internalKey
|
||||
*/
|
||||
private final String key;
|
||||
/**
|
||||
* 业务字段 key
|
||||
*/
|
||||
private final String businessKey;
|
||||
/**
|
||||
* 禁止修改的参数列表
|
||||
*/
|
||||
private final Set<String> disabledProps;
|
||||
/**
|
||||
* 表单 key
|
||||
*/
|
||||
private final String formKey;
|
||||
|
||||
BusinessModuleField(String key, String businessKey, Set<String> disabledProps, String formKey) {
|
||||
this.key = key;
|
||||
this.businessKey = businessKey;
|
||||
this.disabledProps = disabledProps;
|
||||
this.formKey = formKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断业务字段是否被删除
|
||||
*
|
||||
* @param formKey 表单 key
|
||||
* @param fields 字段集合
|
||||
* @return 是否被删除
|
||||
*/
|
||||
public static boolean isBusinessDeleted(String formKey, List<BaseField> fields) {
|
||||
List<BusinessModuleField> formBusinessFields;
|
||||
if (FormKey.ofKey(formKey) == null) {
|
||||
// 如果不是内置表单,则校验自定义表单字段必须要名字和负责人
|
||||
formBusinessFields = List.of(BusinessModuleField.CUSTOM_FORM_DATA_NAME, BusinessModuleField.CUSTOM_FORM_DATA_OWNER);
|
||||
} else {
|
||||
formBusinessFields = Arrays.stream(BusinessModuleField.values()).filter(field -> Strings.CS.equals(formKey, field.getFormKey())).toList();
|
||||
}
|
||||
|
||||
if (CollectionUtils.isEmpty(formBusinessFields)) {
|
||||
return false;
|
||||
}
|
||||
return formBusinessFields.stream()
|
||||
.anyMatch(businessField ->
|
||||
fields.stream().noneMatch(field -> Strings.CS.equals(businessField.getKey(), field.getInternalKey()))
|
||||
&& businessField.noneMatchOfSubFields(fields)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断子表字段中是否存在业务字段
|
||||
*
|
||||
* @param fields 字段集合
|
||||
* @return 是否存在业务字段
|
||||
*/
|
||||
private boolean noneMatchOfSubFields(List<BaseField> fields) {
|
||||
boolean noneMatch = true;
|
||||
for (BaseField field : fields) {
|
||||
if (field instanceof SubField subField && CollectionUtils.isNotEmpty(subField.getSubFields())) {
|
||||
noneMatch = subField.getSubFields().stream().noneMatch(sub -> Strings.CS.equals(this.getKey(), sub.getInternalKey()));
|
||||
if (!noneMatch) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return noneMatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否有重复的字段
|
||||
*
|
||||
* @param fields 字段集合
|
||||
* @return 是否有重复的字段
|
||||
*/
|
||||
public static boolean hasRepeatName(List<BaseField> fields) {
|
||||
return fields.stream()
|
||||
.collect(Collectors.groupingBy(BaseField::getName, Collectors.counting()))
|
||||
.values().stream()
|
||||
.anyMatch(count -> count > 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过Key查询业务字段
|
||||
*
|
||||
* @param internalKey 业务key
|
||||
* @return 业务字段
|
||||
*/
|
||||
public static BusinessModuleField ofKey(String internalKey) {
|
||||
return INTERNAL_CACHE.get(internalKey);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package cn.cordys.common.constants;
|
||||
|
||||
/**
|
||||
* 客户,线索,商机等业务数据的搜索类型
|
||||
*
|
||||
* @author jianxing
|
||||
*/
|
||||
public enum BusinessSearchType {
|
||||
/**
|
||||
* 全部数据
|
||||
*/
|
||||
ALL,
|
||||
/**
|
||||
* 负责人是我的数据
|
||||
*/
|
||||
SELF,
|
||||
/**
|
||||
* 有数据权限的部门的数据
|
||||
*/
|
||||
DEPARTMENT
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package cn.cordys.common.constants;
|
||||
|
||||
/**
|
||||
* @Author: jianxing
|
||||
* @CreateTime: 2025-10-14 10:11
|
||||
*/
|
||||
public enum ChartAggregateMethod {
|
||||
SUM,
|
||||
AVG,
|
||||
COUNT,
|
||||
MAX,
|
||||
MIN
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package cn.cordys.common.constants;
|
||||
|
||||
import cn.cordys.common.exception.IResultCode;
|
||||
|
||||
/**
|
||||
* 通用功能状态码
|
||||
* 通用功能返回的状态码
|
||||
*
|
||||
* @author jianxing
|
||||
*/
|
||||
public enum CommonResultCode implements IResultCode {
|
||||
|
||||
FIELD_VALIDATE_ERROR(100001, "field_validate_error"),
|
||||
FIELD_OPTION_VALUE_ERROR(100002, "field_option_value_error"),
|
||||
APPROVAL_NOT_ENABLED_ERROR(100003, "approval.not.enabled");
|
||||
|
||||
|
||||
private final int code;
|
||||
private final String message;
|
||||
|
||||
CommonResultCode(int code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package cn.cordys.common.constants;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author song-cc-rock
|
||||
*/
|
||||
|
||||
@Getter
|
||||
public enum FormKey {
|
||||
|
||||
/**
|
||||
* 线索
|
||||
*/
|
||||
CLUE("clue"),
|
||||
/**
|
||||
* 客户
|
||||
*/
|
||||
CUSTOMER("customer"),
|
||||
/**
|
||||
* 联系人
|
||||
*/
|
||||
CONTACT("contact"),
|
||||
/**
|
||||
* 跟进记录
|
||||
*/
|
||||
FOLLOW_RECORD("record"),
|
||||
/**
|
||||
* 跟进计划
|
||||
*/
|
||||
FOLLOW_PLAN("plan"),
|
||||
/**
|
||||
* 商机
|
||||
*/
|
||||
OPPORTUNITY("opportunity"),
|
||||
/**
|
||||
* 产品
|
||||
*/
|
||||
PRODUCT("product"),
|
||||
/**
|
||||
* 价格
|
||||
*/
|
||||
PRICE("price"),
|
||||
/**
|
||||
* 报价单
|
||||
*/
|
||||
QUOTATION("quotation"),
|
||||
/**
|
||||
* 合同
|
||||
*/
|
||||
CONTRACT("contract"),
|
||||
/**
|
||||
* 发票
|
||||
*/
|
||||
INVOICE("invoice"),
|
||||
/**
|
||||
* 合同回款计划
|
||||
*/
|
||||
CONTRACT_PAYMENT_PLAN("contractPaymentPlan"),
|
||||
/**
|
||||
* 回款记录
|
||||
*/
|
||||
CONTRACT_PAYMENT_RECORD("contractPaymentRecord"),
|
||||
/**
|
||||
* 订单
|
||||
*/
|
||||
ORDER("order");
|
||||
|
||||
private final String key;
|
||||
|
||||
FormKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public static List<String> allKeys() {
|
||||
return Arrays.stream(FormKey.values()).map(FormKey::getKey).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static FormKey ofKey(String key) {
|
||||
for (FormKey formKey : FormKey.values()) {
|
||||
if (Strings.CI.equals(formKey.getKey(), key)) {
|
||||
return formKey;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean hasSnapshot() {
|
||||
return Strings.CI.equalsAny(this.key, CONTRACT.getKey(), INVOICE.getKey(), QUOTATION.getKey(), ORDER.getKey());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package cn.cordys.common.constants;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author song-cc-rock
|
||||
*/
|
||||
|
||||
@Getter
|
||||
public class FormKeyConstants {
|
||||
|
||||
public static final String ORDER = "order";
|
||||
public static final String CLUE = "clue";
|
||||
public static final String CUSTOMER = "customer";
|
||||
public static final String CONTRACT = "contract";
|
||||
public static final String CONTRACT_INVOICE = "contractInvoice";
|
||||
public static final String CONTRACT_PAYMENT_PLAN = "contractPaymentPlan";
|
||||
public static final String CONTRACT_PAYMENT_RECORD = "contractPaymentRecord";
|
||||
public static final String OPPORTUNITY = "opportunity";
|
||||
public static final String QUOTATION = "quotation";
|
||||
public static final String FOLLOW_PLAN = "plan";
|
||||
public static final String FOLLOW_RECORD = "record";
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package cn.cordys.common.constants;
|
||||
|
||||
public enum HttpMethodConstants {
|
||||
GET,
|
||||
HEAD,
|
||||
POST,
|
||||
PUT,
|
||||
PATCH,
|
||||
DELETE,
|
||||
OPTIONS,
|
||||
TRACE,
|
||||
CONNECT
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.cordys.common.constants;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 系统内置角色ID
|
||||
*
|
||||
* @author jianxing
|
||||
*/
|
||||
@Getter
|
||||
public enum InternalRole {
|
||||
ORG_ADMIN("org_admin"),
|
||||
SALES_MANAGER("sales_manager"),
|
||||
SALES_STAFF("sales_staff");
|
||||
|
||||
private final String value;
|
||||
|
||||
InternalRole(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package cn.cordys.common.constants;
|
||||
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @Author: jianxing
|
||||
* @CreateTime: 2025-07-25 14:05
|
||||
*/
|
||||
public enum InternalUserView {
|
||||
/**
|
||||
* 全部视图
|
||||
*/
|
||||
ALL,
|
||||
/**
|
||||
* 个人视图
|
||||
*/
|
||||
SELF,
|
||||
/**
|
||||
* 部门视图
|
||||
*/
|
||||
DEPARTMENT,
|
||||
/**
|
||||
* 协作客户视图
|
||||
*/
|
||||
CUSTOMER_COLLABORATION,
|
||||
/**
|
||||
* 赢单视图
|
||||
*/
|
||||
OPPORTUNITY_SUCCESS;
|
||||
|
||||
public static final String CURRENT_USER = "CURRENT_USER";
|
||||
|
||||
|
||||
public static List<String> getCurrentUserArrayValue() {
|
||||
List<String> values = new ArrayList<>(0);
|
||||
values.add(CURRENT_USER);
|
||||
return values;
|
||||
}
|
||||
|
||||
public static boolean isInternalUserView(String viewId) {
|
||||
if (StringUtils.isBlank(viewId)) {
|
||||
return false;
|
||||
}
|
||||
return Arrays.stream(InternalUserView.values())
|
||||
.map(InternalUserView::name)
|
||||
.collect(Collectors.toSet()).contains(viewId);
|
||||
}
|
||||
|
||||
public static boolean isAll(String searchType) {
|
||||
return Strings.CS.equals(ALL.name(), searchType);
|
||||
}
|
||||
|
||||
public static boolean isSelf(String searchType) {
|
||||
return Strings.CS.equals(SELF.name(), searchType);
|
||||
}
|
||||
|
||||
public static boolean isDepartment(String searchType) {
|
||||
return Strings.CS.equals(DEPARTMENT.name(), searchType);
|
||||
}
|
||||
|
||||
public static boolean isVisible(String searchType) {
|
||||
return Strings.CS.equals(CUSTOMER_COLLABORATION.name(), searchType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package cn.cordys.common.constants;
|
||||
|
||||
/**
|
||||
* 联动场景
|
||||
*
|
||||
* @author song-cc-rock
|
||||
*/
|
||||
public enum LinkScenarioKey {
|
||||
|
||||
/**
|
||||
* 线索转客户
|
||||
*/
|
||||
CLUE_TO_CUSTOMER,
|
||||
/**
|
||||
* 线索转联系人
|
||||
*/
|
||||
CLUE_TO_CONTACT,
|
||||
/**
|
||||
* 线索转商机
|
||||
*/
|
||||
CLUE_TO_OPPORTUNITY,
|
||||
/**
|
||||
* 客户转商机
|
||||
*/
|
||||
CUSTOMER_TO_OPPORTUNITY,
|
||||
/**
|
||||
* 线索转记录
|
||||
*/
|
||||
CLUE_TO_RECORD,
|
||||
/**
|
||||
* 客户转记录
|
||||
*/
|
||||
CUSTOMER_TO_RECORD,
|
||||
/**
|
||||
* 商机转记录
|
||||
*/
|
||||
OPPORTUNITY_TO_RECORD,
|
||||
/**
|
||||
* 计划转记录
|
||||
*/
|
||||
PLAN_TO_RECORD,
|
||||
/**
|
||||
* 合同转发票
|
||||
*/
|
||||
CONTRACT_TO_INVOICE,
|
||||
/**
|
||||
* 合同转订单
|
||||
*/
|
||||
CONTRACT_TO_ORDER,
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package cn.cordys.common.constants;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public enum ModuleKey {
|
||||
|
||||
/**
|
||||
* 首页
|
||||
*/
|
||||
HOME("home"),
|
||||
/**
|
||||
* 线索管理
|
||||
*/
|
||||
CLUE("clue"),
|
||||
/**
|
||||
* 客户管理
|
||||
*/
|
||||
CUSTOMER("customer"),
|
||||
/**
|
||||
* 商机管理
|
||||
*/
|
||||
BUSINESS("business"),
|
||||
/**
|
||||
* 产品管理
|
||||
*/
|
||||
PRODUCT("product"),
|
||||
/**
|
||||
* 系统设置
|
||||
*/
|
||||
SETTING("setting");
|
||||
|
||||
|
||||
/**
|
||||
* *******************************************
|
||||
* 注意:
|
||||
* 新增菜单不要在moduleKey中添加了
|
||||
* *******************************************
|
||||
*/
|
||||
|
||||
private final String key;
|
||||
|
||||
ModuleKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
package cn.cordys.common.constants;
|
||||
|
||||
/**
|
||||
* @author jianxing
|
||||
* @date 2025-01-03 11:31:40
|
||||
*/
|
||||
public class PermissionConstants {
|
||||
|
||||
/*------ start: SYSTEM_ROLE ------*/
|
||||
public static final String SYSTEM_ROLE_READ = "SYSTEM_ROLE:READ";
|
||||
public static final String SYSTEM_ROLE_ADD = "SYSTEM_ROLE:ADD";
|
||||
public static final String SYSTEM_ROLE_UPDATE = "SYSTEM_ROLE:UPDATE";
|
||||
public static final String SYSTEM_ROLE_DELETE = "SYSTEM_ROLE:DELETE";
|
||||
public static final String SYSTEM_ROLE_ADD_USER = "SYSTEM_ROLE:ADD_USER";
|
||||
public static final String SYSTEM_ROLE_REMOVE_USER = "SYSTEM_ROLE:REMOVE_USER";
|
||||
/*------ end: SYSTEM_ROLE------*/
|
||||
|
||||
|
||||
/*------ start: OPERATION_LOG ------*/
|
||||
public static final String OPERATION_LOG_READ = "OPERATION_LOG:READ";
|
||||
/*------ end: OPERATION_LOG ------*/
|
||||
|
||||
/*------ start: SYSTEM_NOTICE ------*/
|
||||
public static final String SYSTEM_NOTICE_READ = "SYSTEM_NOTICE:READ";
|
||||
public static final String SYSTEM_NOTICE_ADD = "SYSTEM_NOTICE:ADD";
|
||||
public static final String SYSTEM_NOTICE_UPDATE = "SYSTEM_NOTICE:UPDATE";
|
||||
public static final String SYSTEM_NOTICE_DELETE = "SYSTEM_NOTICE:DELETE";
|
||||
/*------ end: SYSTEM_NOTICE ------*/
|
||||
|
||||
/*------ start: SYS_DEPARTMENT ------*/
|
||||
public static final String SYS_ORGANIZATION_READ = "SYS_ORGANIZATION:READ";
|
||||
public static final String SYS_ORGANIZATION_ADD = "SYS_ORGANIZATION:ADD";
|
||||
public static final String SYS_ORGANIZATION_UPDATE = "SYS_ORGANIZATION:UPDATE";
|
||||
public static final String SYS_ORGANIZATION_DELETE = "SYS_ORGANIZATION:DELETE";
|
||||
public static final String SYS_ORGANIZATION_IMPORT = "SYS_ORGANIZATION:IMPORT";
|
||||
public static final String SYS_ORGANIZATION_SYNC = "SYS_ORGANIZATION:SYNC";
|
||||
public static final String SYS_ORGANIZATION_USER_RESET_PASSWORD = "SYS_ORGANIZATION_USER:RESET_PASSWORD";
|
||||
|
||||
/*------ end: SYS_DEPARTMENT ------*/
|
||||
|
||||
/*------ start: SYSTEM_SETTING ------*/
|
||||
public static final String SYSTEM_SETTING_READ = "SYSTEM_SETTING:READ";
|
||||
public static final String SYSTEM_SETTING_UPDATE = "SYSTEM_SETTING:UPDATE";
|
||||
public static final String SYSTEM_SETTING_ADD = "SYSTEM_SETTING:ADD";
|
||||
public static final String SYSTEM_SETTING_DELETE = "SYSTEM_SETTING:DELETE";
|
||||
/*------ end: SYSTEM_SETTING ------*/
|
||||
|
||||
/**
|
||||
* module setting permission
|
||||
*/
|
||||
public static final String MODULE_SETTING_READ = "MODULE_SETTING:READ";
|
||||
public static final String MODULE_SETTING_UPDATE = "MODULE_SETTING:UPDATE";
|
||||
|
||||
|
||||
/*------ start: CUSTOMER_MANAGEMENT------*/
|
||||
public static final String CUSTOMER_MANAGEMENT_READ = "CUSTOMER_MANAGEMENT:READ";
|
||||
public static final String CUSTOMER_MANAGEMENT_ADD = "CUSTOMER_MANAGEMENT:ADD";
|
||||
public static final String CUSTOMER_MANAGEMENT_UPDATE = "CUSTOMER_MANAGEMENT:UPDATE";
|
||||
public static final String CUSTOMER_MANAGEMENT_TRANSFER = "CUSTOMER_MANAGEMENT:TRANSFER";
|
||||
public static final String CUSTOMER_MANAGEMENT_RECYCLE = "CUSTOMER_MANAGEMENT:RECYCLE";
|
||||
public static final String CUSTOMER_MANAGEMENT_DELETE = "CUSTOMER_MANAGEMENT:DELETE";
|
||||
public static final String CUSTOMER_MANAGEMENT_EXPORT = "CUSTOMER_MANAGEMENT:EXPORT";
|
||||
public static final String CUSTOMER_MANAGEMENT_IMPORT = "CUSTOMER_MANAGEMENT:IMPORT";
|
||||
public static final String CUSTOMER_MANAGEMENT_MERGE = "CUSTOMER_MANAGEMENT:MERGE";
|
||||
/*------ end: CUSTOMER_MANAGEMENT ------*/
|
||||
|
||||
|
||||
/*------ start: CUSTOMER_MANAGEMENT_POOL ------*/
|
||||
public static final String CUSTOMER_MANAGEMENT_POOL_READ = "CUSTOMER_MANAGEMENT_POOL:READ";
|
||||
public static final String CUSTOMER_MANAGEMENT_POOL_UPDATE = "CUSTOMER_MANAGEMENT_POOL:UPDATE";
|
||||
public static final String CUSTOMER_MANAGEMENT_POOL_DELETE = "CUSTOMER_MANAGEMENT_POOL:DELETE";
|
||||
public static final String CUSTOMER_MANAGEMENT_POOL_PICK = "CUSTOMER_MANAGEMENT_POOL:PICK";
|
||||
public static final String CUSTOMER_MANAGEMENT_POOL_ASSIGN = "CUSTOMER_MANAGEMENT_POOL:ASSIGN";
|
||||
public static final String CUSTOMER_MANAGEMENT_POOL_EXPORT = "CUSTOMER_MANAGEMENT_POOL:EXPORT";
|
||||
/*------ end: CUSTOMER_MANAGEMENT_POOL ------*/
|
||||
|
||||
|
||||
/*------ start: CUSTOMER_MANAGEMENT_CONTACT ------*/
|
||||
public static final String CUSTOMER_MANAGEMENT_CONTACT_READ = "CUSTOMER_MANAGEMENT_CONTACT:READ";
|
||||
public static final String CUSTOMER_MANAGEMENT_CONTACT_ADD = "CUSTOMER_MANAGEMENT_CONTACT:ADD";
|
||||
public static final String CUSTOMER_MANAGEMENT_CONTACT_UPDATE = "CUSTOMER_MANAGEMENT_CONTACT:UPDATE";
|
||||
public static final String CUSTOMER_MANAGEMENT_CONTACT_DELETE = "CUSTOMER_MANAGEMENT_CONTACT:DELETE";
|
||||
public static final String CUSTOMER_MANAGEMENT_CONTACT_EXPORT = "CUSTOMER_MANAGEMENT_CONTACT:EXPORT";
|
||||
public static final String CUSTOMER_MANAGEMENT_CONTACT_IMPORT = "CUSTOMER_MANAGEMENT_CONTACT:IMPORT";
|
||||
/*------ end: CUSTOMER_MANAGEMENT_CONTACT ------*/
|
||||
|
||||
/*------ start: PRODUCT_MANAGEMENT ------*/
|
||||
public static final String PRODUCT_MANAGEMENT_READ = "PRODUCT_MANAGEMENT:READ";
|
||||
public static final String PRODUCT_MANAGEMENT_ADD = "PRODUCT_MANAGEMENT:ADD";
|
||||
public static final String PRODUCT_MANAGEMENT_UPDATE = "PRODUCT_MANAGEMENT:UPDATE";
|
||||
public static final String PRODUCT_MANAGEMENT_DELETE = "PRODUCT_MANAGEMENT:DELETE";
|
||||
public static final String PRODUCT_MANAGEMENT_IMPORT = "PRODUCT_MANAGEMENT:IMPORT";
|
||||
/*------ end: PRODUCT_MANAGEMENT ------*/
|
||||
|
||||
/*------ start: OPPORTUNITY_MANAGEMENT ------*/
|
||||
public static final String OPPORTUNITY_MANAGEMENT_READ = "OPPORTUNITY_MANAGEMENT:READ";
|
||||
public static final String OPPORTUNITY_MANAGEMENT_ADD = "OPPORTUNITY_MANAGEMENT:ADD";
|
||||
public static final String OPPORTUNITY_MANAGEMENT_UPDATE = "OPPORTUNITY_MANAGEMENT:UPDATE";
|
||||
public static final String OPPORTUNITY_MANAGEMENT_TRANSFER = "OPPORTUNITY_MANAGEMENT:TRANSFER";
|
||||
public static final String OPPORTUNITY_MANAGEMENT_DELETE = "OPPORTUNITY_MANAGEMENT:DELETE";
|
||||
public static final String OPPORTUNITY_MANAGEMENT_EXPORT = "OPPORTUNITY_MANAGEMENT:EXPORT";
|
||||
public static final String OPPORTUNITY_MANAGEMENT_RESIGN = "OPPORTUNITY_MANAGEMENT:RESIGN";
|
||||
public static final String OPPORTUNITY_MANAGEMENT_IMPORT = "OPPORTUNITY_MANAGEMENT:IMPORT";
|
||||
/*------ end: OPPORTUNITY_MANAGEMENT ------*/
|
||||
|
||||
|
||||
/**
|
||||
* clue permission
|
||||
*/
|
||||
/*------ start: CLUE_MANAGEMENT ------*/
|
||||
public static final String CLUE_MANAGEMENT_READ = "CLUE_MANAGEMENT:READ";
|
||||
public static final String CLUE_MANAGEMENT_ADD = "CLUE_MANAGEMENT:ADD";
|
||||
public static final String CLUE_MANAGEMENT_UPDATE = "CLUE_MANAGEMENT:UPDATE";
|
||||
public static final String CLUE_MANAGEMENT_TRANSFER = "CLUE_MANAGEMENT:TRANSFER";
|
||||
public static final String CLUE_MANAGEMENT_RECYCLE = "CLUE_MANAGEMENT:RECYCLE";
|
||||
public static final String CLUE_MANAGEMENT_DELETE = "CLUE_MANAGEMENT:DELETE";
|
||||
public static final String CLUE_MANAGEMENT_EXPORT = "CLUE_MANAGEMENT:EXPORT";
|
||||
public static final String CLUE_MANAGEMENT_IMPORT = "CLUE_MANAGEMENT:IMPORT";
|
||||
/*------ end: CLUE_MANAGEMENT ------*/
|
||||
|
||||
|
||||
/*------ start: CLUE_MANAGEMENT_POOL ------*/
|
||||
public static final String CLUE_MANAGEMENT_POOL_READ = "CLUE_MANAGEMENT_POOL:READ";
|
||||
public static final String CLUE_MANAGEMENT_POOL_DELETE = "CLUE_MANAGEMENT_POOL:DELETE";
|
||||
public static final String CLUE_MANAGEMENT_POOL_PICK = "CLUE_MANAGEMENT_POOL:PICK";
|
||||
public static final String CLUE_MANAGEMENT_POOL_ASSIGN = "CLUE_MANAGEMENT_POOL:ASSIGN";
|
||||
public static final String CLUE_MANAGEMENT_POOL_UPDATE = "CLUE_MANAGEMENT_POOL:UPDATE";
|
||||
public static final String CLUE_MANAGEMENT_POOL_EXPORT = "CLUE_MANAGEMENT_POOL:EXPORT";
|
||||
/*------ end: CLUE_MANAGEMENT_POOL ------*/
|
||||
|
||||
/**
|
||||
* dashboard permission
|
||||
*/
|
||||
public static final String DASHBOARD_READ = "DASHBOARD:READ";
|
||||
public static final String DASHBOARD_ADD = "DASHBOARD:ADD";
|
||||
public static final String DASHBOARD_EDIT = "DASHBOARD:UPDATE";
|
||||
public static final String DASHBOARD_DELETE = "DASHBOARD:DELETE";
|
||||
|
||||
/*------ start: LICENSE ------*/
|
||||
public static final String LICENSE_READ = "LICENSE:READ";
|
||||
public static final String LICENSE_EDIT = "LICENSE:EDIT";
|
||||
/*------ end: LICENSE ------*/
|
||||
|
||||
|
||||
/*------ start: PERSON INFO ------*/
|
||||
public static final String PERSONAL_API_KEY_READ = "PERSONAL_API_KEY:READ";
|
||||
public static final String PERSONAL_API_KEY_ADD = "PERSONAL_API_KEY:ADD";
|
||||
public static final String PERSONAL_API_KEY_UPDATE = "PERSONAL_API_KEY:UPDATE";
|
||||
public static final String PERSONAL_API_KEY_DELETE = "PERSONAL_API_KEY:DELETE";
|
||||
/*------ end: PERSON INFO ------*/
|
||||
|
||||
|
||||
/*------ start: AGENT ------*/
|
||||
public static final String AGENT_READ = "AGENT:READ";
|
||||
public static final String AGENT_ADD = "AGENT:ADD";
|
||||
public static final String AGENT_UPDATE = "AGENT:UPDATE";
|
||||
public static final String AGENT_DELETE = "AGENT:DELETE";
|
||||
/*------ end: AGENT ------*/
|
||||
|
||||
/**
|
||||
* product price permission
|
||||
*/
|
||||
public static final String PRICE_READ = "PRICE:READ";
|
||||
public static final String PRICE_ADD = "PRICE:ADD";
|
||||
public static final String PRICE_UPDATE = "PRICE:UPDATE";
|
||||
public static final String PRICE_DELETE = "PRICE:DELETE";
|
||||
public static final String PRICE_IMPORT = "PRICE:IMPORT";
|
||||
public static final String PRICE_EXPORT = "PRICE:EXPORT";
|
||||
|
||||
|
||||
/*------ start: OPPORTUNITY_QUOTATION ------*/
|
||||
|
||||
public static final String OPPORTUNITY_QUOTATION_READ = "OPPORTUNITY_QUOTATION:READ";
|
||||
public static final String OPPORTUNITY_QUOTATION_ADD = "OPPORTUNITY_QUOTATION:ADD";
|
||||
public static final String OPPORTUNITY_QUOTATION_UPDATE = "OPPORTUNITY_QUOTATION:UPDATE";
|
||||
public static final String OPPORTUNITY_QUOTATION_DELETE = "OPPORTUNITY_QUOTATION:DELETE";
|
||||
public static final String OPPORTUNITY_QUOTATION_DOWNLOAD = "OPPORTUNITY_QUOTATION:DOWNLOAD";
|
||||
public static final String OPPORTUNITY_QUOTATION_VOIDED = "OPPORTUNITY_QUOTATION:VOIDED";
|
||||
public static final String OPPORTUNITY_QUOTATION_APPROVAL = "OPPORTUNITY_QUOTATION:APPROVAL";
|
||||
|
||||
/*------ end: OPPORTUNITY_QUOTATION ------*/
|
||||
|
||||
/*------ start: CONTRACT ------*/
|
||||
public static final String CONTRACT_READ = "CONTRACT:READ";
|
||||
public static final String CONTRACT_ADD = "CONTRACT:ADD";
|
||||
public static final String CONTRACT_UPDATE = "CONTRACT:UPDATE";
|
||||
public static final String CONTRACT_DELETE = "CONTRACT:DELETE";
|
||||
public static final String CONTRACT_EXPORT = "CONTRACT:EXPORT";
|
||||
public static final String CONTRACT_APPROVAL = "CONTRACT:APPROVAL";
|
||||
public static final String CONTRACT_STAGE = "CONTRACT:STAGE";
|
||||
public static final String CONTRACT_PAYMENT = "CONTRACT:PAYMENT";
|
||||
|
||||
/*------ end: CONTRACT ------*/
|
||||
|
||||
/*------ start: CONTRACT_CONTRACT_PAYMENT_PLAN_ROLE ------*/
|
||||
public static final String CONTRACT_PAYMENT_PLAN_READ = "CONTRACT_PAYMENT_PLAN:READ";
|
||||
public static final String CONTRACT_PAYMENT_PLAN_ADD = "CONTRACT_PAYMENT_PLAN:ADD";
|
||||
public static final String CONTRACT_PAYMENT_PLAN_UPDATE = "CONTRACT_PAYMENT_PLAN:UPDATE";
|
||||
public static final String CONTRACT_PAYMENT_PLAN_DELETE = "CONTRACT_PAYMENT_PLAN:DELETE";
|
||||
/*------ end: CONTRACT_CONTRACT_PAYMENT_PLAN_ROLE ------*/
|
||||
|
||||
|
||||
/*------ start: TENDER ------*/
|
||||
public static final String TENDER_READ = "TENDER:READ";
|
||||
/*------ end: TENDER ------*/
|
||||
|
||||
|
||||
/*------ start: CONTRACT_INVOICE_ROLE ------*/
|
||||
public static final String CONTRACT_INVOICE_READ = "CONTRACT_INVOICE:READ";
|
||||
public static final String CONTRACT_INVOICE_ADD = "CONTRACT_INVOICE:ADD";
|
||||
public static final String CONTRACT_INVOICE_UPDATE = "CONTRACT_INVOICE:UPDATE";
|
||||
public static final String CONTRACT_INVOICE_EXPORT = "CONTRACT_INVOICE:EXPORT";
|
||||
public static final String CONTRACT_INVOICE_APPROVAL = "CONTRACT_INVOICE:APPROVAL";
|
||||
public static final String CONTRACT_INVOICE_DELETE = "CONTRACT_INVOICE:DELETE";
|
||||
/*------ end: CONTRACT_INVOICE_ROLE ------*/
|
||||
|
||||
/*------ start: BUSINESS_TITLE ------*/
|
||||
public static final String CONTRACT_BUSINESS_TITLE_READ = "CONTRACT_BUSINESS_TITLE:READ";
|
||||
public static final String CONTRACT_BUSINESS_TITLE_ADD = "CONTRACT_BUSINESS_TITLE:ADD";
|
||||
public static final String CONTRACT_BUSINESS_TITLE_UPDATE = "CONTRACT_BUSINESS_TITLE:UPDATE";
|
||||
public static final String CONTRACT_BUSINESS_TITLE_DELETE = "CONTRACT_BUSINESS_TITLE:DELETE";
|
||||
public static final String CONTRACT_BUSINESS_TITLE_EXPORT = "CONTRACT_BUSINESS_TITLE:EXPORT";
|
||||
public static final String CONTRACT_BUSINESS_TITLE_APPROVAL = "CONTRACT_BUSINESS_TITLE:APPROVAL";
|
||||
public static final String CONTRACT_BUSINESS_TITLE_IMPORT = "CONTRACT_BUSINESS_TITLE:IMPORT";
|
||||
|
||||
/*------ end: BUSINESS_TITLE ------*/
|
||||
|
||||
/**
|
||||
* Contract payment record permission
|
||||
*/
|
||||
public static final String CONTRACT_PAYMENT_RECORD_READ = "CONTRACT_PAYMENT_RECORD:READ";
|
||||
public static final String CONTRACT_PAYMENT_RECORD_ADD = "CONTRACT_PAYMENT_RECORD:ADD";
|
||||
public static final String CONTRACT_PAYMENT_RECORD_UPDATE = "CONTRACT_PAYMENT_RECORD:UPDATE";
|
||||
public static final String CONTRACT_PAYMENT_RECORD_DELETE = "CONTRACT_PAYMENT_RECORD:DELETE";
|
||||
public static final String CONTRACT_PAYMENT_RECORD_IMPORT = "CONTRACT_PAYMENT_RECORD:IMPORT";
|
||||
public static final String CONTRACT_PAYMENT_RECORD_EXPORT = "CONTRACT_PAYMENT_RECORD:EXPORT";
|
||||
|
||||
/*------ start: ORDER_ROLE ------*/
|
||||
public static final String ORDER_READ = "ORDER:READ";
|
||||
public static final String ORDER_ADD = "ORDER:ADD";
|
||||
public static final String ORDER_UPDATE = "ORDER:UPDATE";
|
||||
public static final String ORDER_DELETE = "ORDER:DELETE";
|
||||
public static final String ORDER_DOWNLOAD = "ORDER:DOWNLOAD";
|
||||
/*------ end: ORDER_ROLE ------*/
|
||||
|
||||
/*------ start: PROCESS_SETTING ------*/
|
||||
public static final String PROCESS_SETTING_READ = "PROCESS_SETTING:READ";
|
||||
public static final String PROCESS_SETTING_ADD = "PROCESS_SETTING:ADD";
|
||||
public static final String PROCESS_SETTING_UPDATE = "PROCESS_SETTING:UPDATE";
|
||||
public static final String PROCESS_SETTING_DELETE = "PROCESS_SETTING:DELETE";
|
||||
/*------ end: PROCESS_SETTING ------*/
|
||||
|
||||
/*------ start: CUSTOM_FORM ------*/
|
||||
public static final String CUSTOM_FORM_READ = "CUSTOM_FORM:READ";
|
||||
public static final String CUSTOM_FORM_ADD = "CUSTOM_FORM:ADD";
|
||||
/*------ end: CUSTOM_FORM ------*/
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package cn.cordys.common.constants;
|
||||
|
||||
/**
|
||||
* 角色的数据权限范围
|
||||
*
|
||||
* @Author: jianxing
|
||||
* @CreateTime: 2025-01-07 16:42
|
||||
*/
|
||||
public enum RoleDataScope {
|
||||
/**
|
||||
* 全部数据权限
|
||||
*/
|
||||
ALL,
|
||||
/**
|
||||
* 指定部门数据权限
|
||||
*/
|
||||
DEPT_CUSTOM,
|
||||
/**
|
||||
* 部门及以下数据权限
|
||||
*/
|
||||
DEPT_AND_CHILD,
|
||||
/**
|
||||
* 仅本人数据权限
|
||||
*/
|
||||
SELF
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package cn.cordys.common.constants;
|
||||
|
||||
public class RuleValidatorConstants {
|
||||
|
||||
/**
|
||||
* 必填
|
||||
*/
|
||||
public static final String REQUIRED = "required";
|
||||
/**
|
||||
* 唯一
|
||||
*/
|
||||
public static final String UNIQUE = "unique";
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package cn.cordys.common.constants;
|
||||
|
||||
/**
|
||||
* 部门来源类型
|
||||
*/
|
||||
public enum ThirdConfigTypeConstants {
|
||||
|
||||
/**
|
||||
* 本地
|
||||
*/
|
||||
INTERNAL,
|
||||
/**
|
||||
* 企业微信
|
||||
*/
|
||||
WECOM,
|
||||
/**
|
||||
* 钉钉
|
||||
*/
|
||||
DINGTALK,
|
||||
/**
|
||||
* 飞书
|
||||
*/
|
||||
LARK,
|
||||
/**
|
||||
* DE
|
||||
*/
|
||||
DE,
|
||||
/**
|
||||
* SQLBOT
|
||||
*/
|
||||
SQLBOT,
|
||||
/**
|
||||
* maxKB
|
||||
*/
|
||||
MAXKB,
|
||||
/**
|
||||
* tender
|
||||
*/
|
||||
TENDER,
|
||||
/**
|
||||
* 企查查
|
||||
*/
|
||||
QCC;
|
||||
|
||||
public static ThirdConfigTypeConstants fromString(String type) {
|
||||
try {
|
||||
return ThirdConfigTypeConstants.valueOf(type.toUpperCase());
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package cn.cordys.common.constants;
|
||||
|
||||
public enum ThirdDetailType {
|
||||
WECOM_SYNC, DINGTALK_SYNC, LARK_SYNC, DE_BOARD, SQLBOT_CHAT, SQLBOT_BOARD, MAXKB, TENDER, QCC
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package cn.cordys.common.constants;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TopicConstants {
|
||||
/**
|
||||
* 下载任务的 Redis 主题名称
|
||||
*/
|
||||
public static final String DOWNLOAD_TOPIC = "download-topic";
|
||||
|
||||
/**
|
||||
* sse 消息通知的Redis 主题名称
|
||||
*/
|
||||
public static final String SSE_TOPIC = "sse-topic";
|
||||
|
||||
/**
|
||||
* 所有 Redis 主题的集合
|
||||
* 用于统一管理订阅和发布的主题
|
||||
*/
|
||||
public static final List<String> ALL_TOPICS = List.of(DOWNLOAD_TOPIC, SSE_TOPIC);
|
||||
|
||||
private TopicConstants() {
|
||||
// 私有构造函数,防止实例化
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package cn.cordys.common.constants;
|
||||
|
||||
/**
|
||||
* 用户来源类型枚举类,用于标识用户的来源。
|
||||
* <p>
|
||||
* 此枚举类定义了不同的用户来源类型,包括本地、LDAP、CAS、OIDC、OAuth2 和二维码。
|
||||
* </p>
|
||||
*/
|
||||
public enum UserSource {
|
||||
|
||||
/**
|
||||
* 本地用户来源,表示用户通过本地系统注册和登录。
|
||||
*/
|
||||
LOCAL,
|
||||
|
||||
/**
|
||||
* LDAP 用户来源,表示用户通过 LDAP(轻量目录访问协议)系统认证。
|
||||
*/
|
||||
LDAP,
|
||||
|
||||
/**
|
||||
* CAS 用户来源,表示用户通过 CAS(中央认证服务)认证。
|
||||
*/
|
||||
CAS,
|
||||
|
||||
/**
|
||||
* OIDC 用户来源,表示用户通过 OIDC(开放ID连接)认证。
|
||||
*/
|
||||
OIDC,
|
||||
|
||||
/**
|
||||
* OAUTH2 用户来源,表示用户通过 企业微信OAUTH2 授权框架认证。
|
||||
*/
|
||||
WECOM_OAUTH2,
|
||||
|
||||
/**
|
||||
* OAUTH2 用户来源,表示用户通过 GitHub OAUTH2 授权框架认证。
|
||||
*/
|
||||
GITHUB_OAUTH2,
|
||||
|
||||
/**
|
||||
* 二维码用户来源,表示用户通过扫描二维码登录。
|
||||
*/
|
||||
QR_CODE,
|
||||
|
||||
/**
|
||||
* OAUTH2 用户来源,表示用户通过 钉钉OAUTH2 授权框架认证。
|
||||
*/
|
||||
DINGTALK_OAUTH2,
|
||||
|
||||
/**
|
||||
* OAUTH2 用户来源,表示用户通过 飞书OAUTH2 授权框架认证。
|
||||
*/
|
||||
LARK_OAUTH2
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package cn.cordys.common.context;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface CustomFunction<T, R> {
|
||||
R apply(T t) throws InterruptedException;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package cn.cordys.common.context;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ExportTaskFunction {
|
||||
void apply() throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package cn.cordys.common.context;
|
||||
|
||||
import cn.cordys.context.OrganizationContext;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 组织信息及请求来源的 Web 过滤器
|
||||
* <p>
|
||||
* 根据请求头自动设置组织上下文与请求来源,并在请求结束时清理资源。
|
||||
*
|
||||
* @author jianxing
|
||||
*/
|
||||
public class OrganizationContextWebFilter extends OncePerRequestFilter {
|
||||
|
||||
public static final String ORGANIZATION_ID_HEADER = "Organization-Id";
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
||||
throws ServletException, IOException {
|
||||
// 提取所有头信息
|
||||
String organizationId = request.getHeader(ORGANIZATION_ID_HEADER);
|
||||
|
||||
// 设置组织 ID
|
||||
if (StringUtils.isNotBlank(organizationId)) {
|
||||
OrganizationContext.setOrganizationId(organizationId);
|
||||
}
|
||||
|
||||
try {
|
||||
chain.doFilter(request, response);
|
||||
} finally {
|
||||
// 保证上下文清理,避免内存泄漏
|
||||
OrganizationContext.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package cn.cordys.common.context;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 数据源详情解析上下文
|
||||
*
|
||||
* @author song-cc-rock
|
||||
*/
|
||||
public class SourceDetailResolveContext {
|
||||
|
||||
private static final ThreadLocal<Map<String, Map<String, Object>>> CONTEXT =
|
||||
ThreadLocal.withInitial(HashMap::new);
|
||||
private static final ThreadLocal<Integer> DEPTH =
|
||||
ThreadLocal.withInitial(() -> 0);
|
||||
|
||||
public static Map<String, Map<String, Object>> getSourceMap() {
|
||||
return CONTEXT.get();
|
||||
}
|
||||
|
||||
public static boolean contains(String sourceId) {
|
||||
return CONTEXT.get().containsKey(sourceId);
|
||||
}
|
||||
|
||||
public static void putPlaceholder(String sourceId) {
|
||||
CONTEXT.get().putIfAbsent(sourceId, new HashMap<>(8));
|
||||
}
|
||||
|
||||
public static void put(String sourceId, Map<String, Object> detail) {
|
||||
CONTEXT.get().put(sourceId, detail);
|
||||
}
|
||||
|
||||
public static void start() {
|
||||
DEPTH.set(DEPTH.get() + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前深度
|
||||
*
|
||||
* @return 当前深度
|
||||
*/
|
||||
public static int getDepth() {
|
||||
return DEPTH.get();
|
||||
}
|
||||
|
||||
public static void end() {
|
||||
int depth = DEPTH.get() - 1;
|
||||
if (depth <= 0) {
|
||||
clear();
|
||||
DEPTH.remove();
|
||||
} else {
|
||||
DEPTH.set(depth);
|
||||
}
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
CONTEXT.remove();
|
||||
}
|
||||
|
||||
public static void remove(String sourceId) {
|
||||
CONTEXT.get().remove(sourceId);
|
||||
}
|
||||
|
||||
private SourceDetailResolveContext() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.cordys.common.domain;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class BaseModel implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String id;
|
||||
@Schema(description = "创建人")
|
||||
private String createUser;
|
||||
@Schema(description = "修改人")
|
||||
private String updateUser;
|
||||
@Schema(description = "创建时间")
|
||||
private Long createTime;
|
||||
@Schema(description = "更新时间")
|
||||
private Long updateTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package cn.cordys.common.domain;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author jianxing
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class BaseModuleFieldValue implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "自定义属性id")
|
||||
private String fieldId;
|
||||
|
||||
/**
|
||||
* 可能是数组
|
||||
*/
|
||||
@Schema(description = "自定义属性值")
|
||||
private Object fieldValue;
|
||||
|
||||
public boolean valid() {
|
||||
return switch (fieldValue) {
|
||||
case null -> false;
|
||||
case String fieldValueStr when StringUtils.isBlank(fieldValueStr) -> false;
|
||||
case List fieldValueList when CollectionUtils.isEmpty(fieldValueList) -> false;
|
||||
default -> true;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package cn.cordys.common.domain;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author jianxing
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class BaseResourceField extends BaseModuleFieldValue {
|
||||
@Schema(description = "ID")
|
||||
private String id;
|
||||
|
||||
@Schema(description = "资源ID")
|
||||
private String resourceId;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.cordys.common.domain;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author song-cc-rock
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class BaseResourceSubField extends BaseResourceField {
|
||||
|
||||
@Schema(description = "关联子表格ID")
|
||||
private String refSubId;
|
||||
|
||||
@Schema(description = "行ID")
|
||||
private String rowId;
|
||||
|
||||
@Schema(description = "行唯一标识")
|
||||
private String bizId;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package cn.cordys.common.dto;
|
||||
|
||||
import cn.cordys.common.dto.condition.BaseCondition;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* <p>表示分页请求的 DTO 类,继承自 {@link BaseCondition} 类,包含了分页参数和排序字段。</p>
|
||||
* <p>用于分页查询时传递当前页码、每页条数和排序信息。</p>
|
||||
*/
|
||||
@Data
|
||||
public class BasePageRequest extends BaseCondition {
|
||||
|
||||
/**
|
||||
* 当前页码,最小值为 1
|
||||
*/
|
||||
@Min(value = 1, message = "当前页码必须大于0")
|
||||
@Schema(description = "当前页码")
|
||||
private int current;
|
||||
|
||||
/**
|
||||
* 每页显示条数,范围为 1 到 500
|
||||
*/
|
||||
@Min(value = 1, message = "每页显示条数必须不小于1")
|
||||
@Max(value = 500, message = "每页显示条数不能大于500")
|
||||
@Schema(description = "每页显示条数")
|
||||
private int pageSize;
|
||||
|
||||
@Valid
|
||||
@Schema(description = "排序字段")
|
||||
private SortRequest sort;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package cn.cordys.common.dto;
|
||||
|
||||
import cn.cordys.common.util.JSON;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author: jianxing
|
||||
* @CreateTime: 2025-09-25 11:37
|
||||
*/
|
||||
@Data
|
||||
public class BatchUpdateDbParam {
|
||||
private List<String> ids;
|
||||
private String fieldName;
|
||||
private Object fieldValue;
|
||||
private String updateUser;
|
||||
private Long updateTime;
|
||||
|
||||
public Object getFieldValue() {
|
||||
if (fieldValue != null && fieldValue instanceof List) {
|
||||
return JSON.toJSONString(fieldValue);
|
||||
}
|
||||
return fieldValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package cn.cordys.common.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class BusinessDataPermission extends DeptDataPermissionDTO {
|
||||
|
||||
@Schema(description = "数据来源表")
|
||||
private String sourceTable;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package cn.cordys.common.dto;
|
||||
|
||||
import cn.cordys.common.dto.chart.ChartCategoryAxisDbParam;
|
||||
import cn.cordys.common.dto.chart.ChartValueAxisDbParam;
|
||||
import cn.cordys.common.dto.condition.CombineSearch;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author: jianxing
|
||||
* @CreateTime: 2025-10-13 14:34
|
||||
*/
|
||||
@Data
|
||||
public class ChartAnalysisDbRequest extends ChartAnalysisRequest {
|
||||
|
||||
@Schema(description = "过滤条件")
|
||||
private CombineSearch viewFilterCondition;
|
||||
|
||||
/**
|
||||
* x轴查询参数
|
||||
*/
|
||||
private ChartCategoryAxisDbParam categoryAxisParam;
|
||||
|
||||
/**
|
||||
* x轴子类别查询参数
|
||||
*/
|
||||
private ChartCategoryAxisDbParam subCategoryAxisParam;
|
||||
|
||||
/**
|
||||
* y轴查询参数
|
||||
*/
|
||||
private ChartValueAxisDbParam valueAxisParam;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package cn.cordys.common.dto;
|
||||
|
||||
import cn.cordys.common.dto.chart.ChartConfig;
|
||||
import cn.cordys.common.dto.condition.CombineSearch;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author: jianxing
|
||||
* @CreateTime: 2025-10-13 14:34
|
||||
*/
|
||||
@Data
|
||||
public class ChartAnalysisRequest {
|
||||
|
||||
@Schema(description = "视图ID")
|
||||
private String viewId;
|
||||
|
||||
@Schema(description = "过滤条件")
|
||||
@Valid
|
||||
private CombineSearch filterCondition;
|
||||
|
||||
@Schema(description = "搜索条件,支持组合搜索")
|
||||
@NotNull
|
||||
@Valid
|
||||
private ChartConfig chartConfig;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package cn.cordys.common.dto;
|
||||
|
||||
import cn.cordys.common.constants.InternalUserView;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 部门的数据权限
|
||||
*
|
||||
* @author jianxing
|
||||
*/
|
||||
@Data
|
||||
public class DeptDataPermissionDTO {
|
||||
|
||||
/**
|
||||
* 搜索类型(ALL/SELF/DEPARTMENT/VISIBLE)
|
||||
* {@link InternalUserView}
|
||||
*/
|
||||
private String viewId;
|
||||
/**
|
||||
* 是否可查看全部数据
|
||||
*/
|
||||
private Boolean all = false;
|
||||
/**
|
||||
* 是否可查看自己的数据
|
||||
*/
|
||||
private Boolean self = false;
|
||||
/**
|
||||
* 是否被设置为可见
|
||||
*/
|
||||
private Boolean visible = false;
|
||||
/**
|
||||
* 可查看的部门Id
|
||||
*/
|
||||
private Set<String> deptIds = new HashSet<>();
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package cn.cordys.common.dto;
|
||||
|
||||
import cn.cordys.common.domain.BaseModuleFieldValue;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
|
||||
/**
|
||||
* @author jianxing
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class EnableFieldValue extends BaseModuleFieldValue {
|
||||
|
||||
|
||||
@Schema(description = "是否启用")
|
||||
private Boolean enable;
|
||||
|
||||
public boolean valid() {
|
||||
return super.valid() && BooleanUtils.isTrue(enable);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package cn.cordys.common.dto;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class ExportDTO {
|
||||
private String userId;
|
||||
private String orgId;
|
||||
/**
|
||||
* {@link cn.cordys.crm.system.constants.ExportConstants.ExportType}
|
||||
*/
|
||||
private String exportType;
|
||||
private String logModule;
|
||||
private Locale locale;
|
||||
private String fileName;
|
||||
private List<ExportHeadDTO> headList;
|
||||
private DeptDataPermissionDTO deptDataPermission;
|
||||
private BasePageRequest pageRequest;
|
||||
private List<String> selectIds;
|
||||
private ExportSelectRequest selectRequest;
|
||||
private String formKey;
|
||||
/**
|
||||
* 导出字段参数 (通用参数无需设置)
|
||||
*/
|
||||
private ExportFieldParam exportFieldParam;
|
||||
private List<String> mergeHeads;
|
||||
private List<FieldExportMeta> exportMetas;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package cn.cordys.common.dto;
|
||||
|
||||
import cn.cordys.crm.system.dto.field.base.BaseField;
|
||||
import cn.cordys.crm.system.dto.response.ModuleFormConfigDTO;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author song-cc-rock
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
public class ExportFieldParam {
|
||||
|
||||
/**
|
||||
* 子表格ID集合
|
||||
*/
|
||||
private Set<String> subIds;
|
||||
|
||||
/**
|
||||
* 字段配置
|
||||
*/
|
||||
private Map<String, BaseField> fieldConfigMap;
|
||||
|
||||
/**
|
||||
* 表单配置
|
||||
*/
|
||||
private ModuleFormConfigDTO formConfig;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package cn.cordys.common.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ExportHeadDTO {
|
||||
|
||||
@Schema(description = "key")
|
||||
private String key;
|
||||
@Schema(description = "表头名称")
|
||||
private String title;
|
||||
@Schema(description = "字段类型")
|
||||
private String columnType;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.cordys.common.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class ExportSelectRequest {
|
||||
|
||||
@Schema(description = "文件名")
|
||||
private String fileName;
|
||||
|
||||
@Schema(description = "表头信息")
|
||||
@NotEmpty(message = "{export_head_list_is_empty}")
|
||||
private List<ExportHeadDTO> headList;
|
||||
|
||||
@Schema(description = "勾选的数据id集合")
|
||||
@NotEmpty(message = "{export_select_ids_is_empty}")
|
||||
private List<String> ids;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package cn.cordys.common.dto;
|
||||
|
||||
import cn.cordys.common.resolver.field.AbstractModuleFieldResolver;
|
||||
import cn.cordys.crm.system.dto.field.base.BaseField;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 导出字段元数据 (预处理)
|
||||
* @author song-cc-rock
|
||||
*/
|
||||
@Data
|
||||
public class FieldExportMeta {
|
||||
|
||||
private String head;
|
||||
|
||||
private BaseField field;
|
||||
|
||||
private AbstractModuleFieldResolver<?> resolver;
|
||||
|
||||
private boolean noResource;
|
||||
|
||||
private String fieldId;
|
||||
|
||||
private String businessKey;
|
||||
|
||||
private String prefixId;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package cn.cordys.common.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class RedisMessage {
|
||||
/**
|
||||
* redis 发布订阅消息主体
|
||||
*/
|
||||
private String message;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package cn.cordys.common.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 客户、商机、线索是否显示所有数据和部门数据 tab
|
||||
*
|
||||
* @Author: jianxing
|
||||
* @CreateTime: 2025-05-15 14:54
|
||||
*/
|
||||
@Data
|
||||
public class ResourceTabEnableDTO {
|
||||
@Schema(description = "是否显示所有数据tab")
|
||||
private Boolean all = false;
|
||||
@Schema(description = "是否显示部门数据tab")
|
||||
private Boolean dept = false;
|
||||
|
||||
/**
|
||||
* 合并权限
|
||||
*
|
||||
* @param other 其余数据权限配置
|
||||
*
|
||||
* @return 合并后的数据权限配置
|
||||
*/
|
||||
public ResourceTabEnableDTO or(ResourceTabEnableDTO other) {
|
||||
if (other != null) {
|
||||
all |= other.all;
|
||||
dept |= other.dept;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package cn.cordys.common.dto;
|
||||
|
||||
import cn.cordys.common.utils.SqlInjectionChecker;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
|
||||
/**
|
||||
* @author jianxing
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SortRequest {
|
||||
|
||||
@Pattern(regexp = "^[A-Za-z0-9]+$")
|
||||
@Schema(description = "排序字段")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "排序类型(asc/desc)")
|
||||
private String type;
|
||||
|
||||
public static String camelToUnderline(String camelCase) {
|
||||
if (camelCase == null || camelCase.isEmpty()) {
|
||||
return camelCase;
|
||||
}
|
||||
|
||||
// 使用正则表达式将驼峰转换为下划线
|
||||
String underline = camelCase.replaceAll("([A-Z])", "_$1").toLowerCase();
|
||||
|
||||
// 如果开头有下划线,去掉
|
||||
if (underline.startsWith("_")) {
|
||||
underline = underline.substring(1);
|
||||
}
|
||||
|
||||
return underline;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
if (SqlInjectionChecker.containsSqlInjectionRisk(name)) {
|
||||
return "1";
|
||||
}
|
||||
return camelToUnderline(name);
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
if (Strings.CI.equals(type, "asc")) {
|
||||
return "asc";
|
||||
} else {
|
||||
return "desc";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mapper 中调用
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean valid() {
|
||||
return StringUtils.isNotBlank(name) && !SqlInjectionChecker.containsSqlInjectionRisk(name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.cordys.common.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author jianxing
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class UserDeptDTO {
|
||||
|
||||
@Schema(description = "用户ID")
|
||||
private String userId;
|
||||
|
||||
@Schema(description = "部门ID")
|
||||
private String deptId;
|
||||
|
||||
@Schema(description = "部门名称")
|
||||
private String deptName;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package cn.cordys.common.dto.chart;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author: jianxing
|
||||
* @CreateTime: 2025-10-13 14:54
|
||||
*/
|
||||
@Data
|
||||
public class ChartCategoryAxisConfig {
|
||||
@NotBlank
|
||||
@Schema(description = "字段ID")
|
||||
private String fieldId;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package cn.cordys.common.dto.chart;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author: jianxing
|
||||
* @CreateTime: 2025-10-13 14:54
|
||||
*/
|
||||
@Data
|
||||
public class ChartCategoryAxisDbParam extends ChartCategoryAxisConfig {
|
||||
/**
|
||||
* 是否要查blob表
|
||||
*/
|
||||
private Boolean blob = false;
|
||||
/**
|
||||
* 是否是业务字段
|
||||
*/
|
||||
private Boolean businessField = false;
|
||||
/**
|
||||
* 业务字段名称
|
||||
*/
|
||||
private String businessFieldName;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package cn.cordys.common.dto.chart;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author: jianxing
|
||||
* @CreateTime: 2025-10-13 14:34
|
||||
*/
|
||||
@Data
|
||||
public class ChartConfig {
|
||||
|
||||
@Schema(description = "图表类型")
|
||||
private String chatType;
|
||||
|
||||
@Schema(description = "类别轴配置")
|
||||
@NotNull
|
||||
@Valid
|
||||
private ChartCategoryAxisConfig categoryAxis;
|
||||
|
||||
@Schema(description = "子类别轴配置")
|
||||
@Valid
|
||||
private ChartCategoryAxisConfig subCategoryAxis;
|
||||
|
||||
@Schema(description = "值轴配置")
|
||||
@NotNull
|
||||
@Valid
|
||||
private ChartValueAxisConfig valueAxis;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package cn.cordys.common.dto.chart;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author: jianxing
|
||||
* @CreateTime: 2025-10-15 11:40
|
||||
*/
|
||||
@Data
|
||||
public class ChartResult {
|
||||
private String categoryAxis;
|
||||
private String categoryAxisName;
|
||||
private String subCategoryAxis;
|
||||
private String subCategoryAxisName;
|
||||
private Object valueAxis;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package cn.cordys.common.dto.chart;
|
||||
|
||||
import cn.cordys.common.constants.ChartAggregateMethod;
|
||||
import cn.cordys.common.constants.EnumValue;
|
||||
import cn.cordys.common.uid.utils.EnumUtils;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author: jianxing
|
||||
* @CreateTime: 2025-10-13 14:54
|
||||
*/
|
||||
@Data
|
||||
public class ChartValueAxisConfig {
|
||||
@Schema(description = "字段ID")
|
||||
private String fieldId;
|
||||
|
||||
@EnumValue(enumClass = ChartAggregateMethod.class)
|
||||
@Schema(description = "聚合方式")
|
||||
private String aggregateMethod;
|
||||
|
||||
public String getAggregateMethod() {
|
||||
if (this.aggregateMethod == null) {
|
||||
return ChartAggregateMethod.COUNT.name();
|
||||
}
|
||||
// 避免mapper中sql注入
|
||||
return EnumUtils.valueOf(ChartAggregateMethod.class, this.aggregateMethod).name();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.cordys.common.dto.chart;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author: jianxing
|
||||
* @CreateTime: 2025-10-13 14:54
|
||||
*/
|
||||
@Data
|
||||
public class ChartValueAxisDbParam extends ChartValueAxisConfig {
|
||||
/**
|
||||
* 是否要查blob表
|
||||
*/
|
||||
private Boolean blob = false;
|
||||
/**
|
||||
* 是否是业务字段
|
||||
*/
|
||||
private Boolean businessField = false;
|
||||
|
||||
/**
|
||||
* 业务字段名称
|
||||
*/
|
||||
private String businessFieldName;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package cn.cordys.common.dto.condition;
|
||||
|
||||
import cn.cordys.common.utils.ConditionFilterUtils;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 表示 CRM 系统中的基础条件类,用于支持过滤和搜索操作。
|
||||
*/
|
||||
@Data
|
||||
public class BaseCondition {
|
||||
|
||||
@Schema(description = "视图ID")
|
||||
private String viewId;
|
||||
|
||||
@Schema(description = "关键字,用于搜索匹配")
|
||||
private String keyword;
|
||||
|
||||
@Schema(description = "筛选条件列表,用于定义多个搜索条件")
|
||||
@Valid
|
||||
private List<FilterCondition> filters;
|
||||
|
||||
@Schema(description = "高级搜索条件,支持组合搜索")
|
||||
@Valid
|
||||
private CombineSearch combineSearch;
|
||||
|
||||
private CombineSearch viewCombineSearch;
|
||||
|
||||
/**
|
||||
* 转义关键字中的特殊字符。
|
||||
*
|
||||
* @param keyword 输入的关键字
|
||||
*
|
||||
* @return 转义后的关键字
|
||||
*/
|
||||
public static String transferKeyword(String keyword) {
|
||||
if (Strings.CS.contains(keyword, "\\") && !Strings.CS.contains(keyword, "\\\\")) {
|
||||
keyword = Strings.CS.replace(keyword, "\\", "\\\\");
|
||||
}
|
||||
// 判断是否已经转义过,未转义才进行转义。
|
||||
if (Strings.CS.contains(keyword, "%") && !Strings.CS.contains(keyword, "\\%")) {
|
||||
keyword = Strings.CS.replace(keyword, "%", "\\%");
|
||||
}
|
||||
if (Strings.CS.contains(keyword, "_") && !Strings.CS.contains(keyword, "\\_")) {
|
||||
keyword = Strings.CS.replace(keyword, "_", "\\_");
|
||||
}
|
||||
return keyword;
|
||||
}
|
||||
|
||||
public CombineSearch getCombineSearch() {
|
||||
return combineSearch == null ? new CombineSearch() : combineSearch;
|
||||
}
|
||||
|
||||
public List<FilterCondition> getFilters() {
|
||||
return ConditionFilterUtils.getValidConditions(filters);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化关键字,直接设置字段值。
|
||||
*
|
||||
* @param keyword 初始化的关键字
|
||||
*/
|
||||
public void initKeyword(String keyword) {
|
||||
this.keyword = keyword;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package cn.cordys.common.dto.condition;
|
||||
|
||||
import cn.cordys.common.constants.EnumValue;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 表示组合搜索条件,用于支持复杂的搜索逻辑。
|
||||
* 包含匹配模式(所有/任一)和筛选条件列表。
|
||||
*/
|
||||
@Data
|
||||
public class CombineSearch {
|
||||
|
||||
@Schema(description = "匹配模式,支持“所有”或“任一”", allowableValues = {"AND", "OR"})
|
||||
@EnumValue(enumClass = SearchMode.class)
|
||||
private String searchMode = SearchMode.AND.name();
|
||||
|
||||
@Schema(description = "筛选条件列表,用于定义多个搜索条件")
|
||||
@Valid
|
||||
private List<FilterCondition> conditions;
|
||||
|
||||
public List<FilterCondition> getConditions() {
|
||||
if (CollectionUtils.isEmpty(conditions)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return conditions.stream()
|
||||
.filter(FilterCondition::valid)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的匹配模式。如果未设置,则默认返回 "AND"。
|
||||
*
|
||||
* @return 当前的匹配模式
|
||||
*/
|
||||
public String getSearchMode() {
|
||||
return StringUtils.isBlank(searchMode) ? SearchMode.AND.name() : searchMode;
|
||||
}
|
||||
|
||||
public CombineSearch convert() {
|
||||
if (CollectionUtils.isEmpty(conditions)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
Iterator<FilterCondition> iterator = conditions.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
FilterCondition condition = iterator.next();
|
||||
if (!condition.valid()) {
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
Object value = condition.getCombineValue();
|
||||
boolean isBetween = Strings.CS.equals(condition.getCombineOperator(), FilterCondition.CombineConditionOperator.BETWEEN.name());
|
||||
|
||||
if (value instanceof List<?> valueList) {
|
||||
if (CollectionUtils.isEmpty(valueList)) {
|
||||
/*
|
||||
* 兜底处理, 防止前端[EMPTY, NOT_EMPTY]条件产生脏数据导致报错
|
||||
*/
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
// 多值处理
|
||||
if (!condition.expectMulti()) {
|
||||
condition.setValue(valueList.getFirst());
|
||||
}
|
||||
if (isBetween) {
|
||||
Object first = valueList.getFirst();
|
||||
condition.setValue(List.of(first, first));
|
||||
}
|
||||
} else {
|
||||
// 单值处理
|
||||
if (condition.expectMulti()) {
|
||||
if (isBetween) {
|
||||
condition.setValue(List.of(value, value));
|
||||
} else {
|
||||
condition.setValue(List.of(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 枚举:搜索模式,定义了“所有”与“任一”两种匹配模式。
|
||||
*/
|
||||
public enum SearchMode {
|
||||
/**
|
||||
* 所有条件都匹配(“与”操作)
|
||||
*/
|
||||
AND,
|
||||
|
||||
/**
|
||||
* 任一条件匹配(“或”操作)
|
||||
*/
|
||||
OR
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,472 @@
|
||||
package cn.cordys.common.dto.condition;
|
||||
|
||||
import cn.cordys.common.constants.EnumValue;
|
||||
import cn.cordys.common.exception.GenericException;
|
||||
import cn.cordys.common.utils.SqlInjectionChecker;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
|
||||
import java.time.*;
|
||||
import java.time.temporal.TemporalAdjusters;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 表示组合条件,用于支持复杂的过滤和查询逻辑。
|
||||
* 包含字段名、操作符和期望值等信息。
|
||||
*/
|
||||
@Data
|
||||
public class FilterCondition {
|
||||
|
||||
/**
|
||||
* 系统字段为字段名
|
||||
* 模块字段为字段ID
|
||||
*/
|
||||
@Schema(description = "条件的参数名称")
|
||||
@NotNull
|
||||
private String name;
|
||||
|
||||
@Schema(description = "期望值,若操作符为 BETWEEN, IN, NOT_IN 时为数组,其他操作符为单个值")
|
||||
private Object value;
|
||||
|
||||
@Schema(description = "是否是多选值")
|
||||
@NotNull
|
||||
private Boolean multipleValue = false;
|
||||
|
||||
@Schema(description = "操作符",
|
||||
allowableValues = {"IN", "NOT_IN", "BETWEEN", "GT", "LT", "GE", "LE", "COUNT_GT", "COUNT_LT", "EQUALS", "NOT_EQUALS", "CONTAINS", "NOT_CONTAINS", "EMPTY", "NOT_EMPTY"})
|
||||
@EnumValue(enumClass = CombineConditionOperator.class)
|
||||
private String operator;
|
||||
|
||||
@Schema(description = "类型")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "包含新增子部门集合")
|
||||
private List<String> containChildIds;
|
||||
|
||||
public String getName() {
|
||||
if (SqlInjectionChecker.containsSqlInjectionRisk(name)) {
|
||||
throw new GenericException("condition name illegal");
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验条件是否合法,检查字段名称、操作符和值的有效性。
|
||||
*
|
||||
* @return 如果条件合法则返回 true,否则返回 false
|
||||
*/
|
||||
public boolean valid() {
|
||||
if (StringUtils.isBlank(name) || StringUtils.isBlank(operator) || SqlInjectionChecker.containsSqlInjectionRisk(name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 针对空值判断操作符
|
||||
if (Strings.CS.equalsAny(operator, CombineConditionOperator.EMPTY.name(), CombineConditionOperator.NOT_EMPTY.name(), CombineConditionOperator.NOT_EQUAL_ORIGINAL.name())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 针对值为集合类型的校验
|
||||
if (value instanceof List<?> valueList && CollectionUtils.isEmpty(valueList)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 针对值为字符串的校验
|
||||
return !(value instanceof String valueStr) || !StringUtils.isBlank(valueStr);
|
||||
}
|
||||
|
||||
public boolean expectMulti() {
|
||||
return Strings.CS.equalsAny(operator, CombineConditionOperator.IN.name(), CombineConditionOperator.NOT_IN.name(), CombineConditionOperator.BETWEEN.name(), CombineConditionOperator.DYNAMICS.name());
|
||||
}
|
||||
|
||||
|
||||
public Object getCombineValue() {
|
||||
if (Strings.CI.equals(operator, CombineConditionOperator.DYNAMICS.name())) {
|
||||
// value 转为string 类型
|
||||
String strValue = (String) value;
|
||||
String[] split = strValue.split(",");
|
||||
if (split.length == 1) {
|
||||
String dateValue = split[0];
|
||||
switch (dateValue) {
|
||||
case "TODAY" -> {
|
||||
List<Long> todayList = new ArrayList<>();
|
||||
// 获取今天的日期
|
||||
LocalDate today = LocalDate.now();
|
||||
long timestamp = getTimestamp(today.atStartOfDay());
|
||||
todayList.add(timestamp);
|
||||
long timestampEnd = getTimestamp(today.atTime(23, 59, 59, 999_000_000));
|
||||
todayList.add(timestampEnd);
|
||||
return todayList;
|
||||
}
|
||||
case "YESTERDAY" -> {
|
||||
List<Long> yesterdayList = new ArrayList<>();
|
||||
LocalDate yesterday = LocalDate.now().minusDays(1);
|
||||
long timestamp = getTimestamp(yesterday.atStartOfDay());
|
||||
yesterdayList.add(timestamp);
|
||||
long timestampEnd = getTimestamp(yesterday.atTime(23, 59, 59, 999_000_000));
|
||||
yesterdayList.add(timestampEnd);
|
||||
return yesterdayList;
|
||||
}
|
||||
case "TOMORROW" -> {
|
||||
List<Long> tomorrowList = new ArrayList<>();
|
||||
LocalDate tomorrow = LocalDate.now().plusDays(1);
|
||||
long timestamp = getTimestamp(tomorrow.atStartOfDay());
|
||||
tomorrowList.add(timestamp);
|
||||
long timestampEnd = getTimestamp(tomorrow.atTime(23, 59, 59, 999_000_000));
|
||||
tomorrowList.add(timestampEnd);
|
||||
return tomorrowList;
|
||||
}
|
||||
case "WEEK" -> {
|
||||
List<Long> weeks = new ArrayList<>();
|
||||
LocalDate startOfWeek = LocalDate.now().with(java.time.DayOfWeek.MONDAY);
|
||||
long timestamp = getTimestamp(startOfWeek.atStartOfDay());
|
||||
weeks.add(timestamp);
|
||||
LocalDate now = LocalDate.now().with(DayOfWeek.SUNDAY);
|
||||
long timestampEnd = getTimestamp(now.atTime(23, 59, 59, 999_000_000));
|
||||
weeks.add(timestampEnd);
|
||||
return weeks;
|
||||
}
|
||||
case "LAST_WEEK" -> {
|
||||
List<Long> lastWeeks = new ArrayList<>();
|
||||
LocalDate startOfLastWeek = LocalDate.now().minusWeeks(1).with(java.time.DayOfWeek.MONDAY);
|
||||
long timestamp = getTimestamp(startOfLastWeek.atStartOfDay());
|
||||
lastWeeks.add(timestamp);
|
||||
LocalDate startOfLastWeekEnd = LocalDate.now().minusWeeks(1).with(DayOfWeek.SUNDAY);
|
||||
long timestampEnd = getTimestamp(startOfLastWeekEnd.atTime(23, 59, 59, 999_000_000));
|
||||
lastWeeks.add(timestampEnd);
|
||||
return lastWeeks;
|
||||
}
|
||||
case "NEXT_WEEK" -> {
|
||||
List<Long> nextWeeks = new ArrayList<>();
|
||||
LocalDate startOfNextWeek = LocalDate.now().plusWeeks(1).with(java.time.DayOfWeek.MONDAY);
|
||||
long timestamp = getTimestamp(startOfNextWeek.atStartOfDay());
|
||||
nextWeeks.add(timestamp);
|
||||
LocalDate startOfNextWeekEnd = LocalDate.now().plusWeeks(1).with(DayOfWeek.SUNDAY);
|
||||
long timestampEnd = getTimestamp(startOfNextWeekEnd.atTime(23, 59, 59, 999_000_000));
|
||||
nextWeeks.add(timestampEnd);
|
||||
return nextWeeks;
|
||||
}
|
||||
case "MONTH" -> {
|
||||
List<Long> months = new ArrayList<>();
|
||||
LocalDate startOfMonth = LocalDate.now().withDayOfMonth(1);
|
||||
long timestamp = getTimestamp(startOfMonth.atStartOfDay());
|
||||
months.add(timestamp);
|
||||
LocalDate now = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth());
|
||||
long timestampEnd = getTimestamp(now.atTime(23, 59, 59, 999_000_000));
|
||||
months.add(timestampEnd);
|
||||
return months;
|
||||
}
|
||||
case "LAST_MONTH" -> {
|
||||
List<Long> lastMonths = new ArrayList<>();
|
||||
LocalDate startOfLastMonth = LocalDate.now().minusMonths(1).withDayOfMonth(1);
|
||||
long timestamp = getTimestamp(startOfLastMonth.atStartOfDay());
|
||||
lastMonths.add(timestamp);
|
||||
LocalDate startOfLastMonthEnd = LocalDate.now().minusMonths(1).with(TemporalAdjusters.lastDayOfMonth());
|
||||
long timestampEnd = getTimestamp(startOfLastMonthEnd.atTime(23, 59, 59, 999_000_000));
|
||||
lastMonths.add(timestampEnd);
|
||||
return lastMonths;
|
||||
}
|
||||
case "NEXT_MONTH" -> {
|
||||
List<Long> nextMonths = new ArrayList<>();
|
||||
LocalDate startOfNextMonth = LocalDate.now().plusMonths(1).withDayOfMonth(1);
|
||||
long timestamp = getTimestamp(startOfNextMonth.atStartOfDay());
|
||||
nextMonths.add(timestamp);
|
||||
LocalDate startOfNextMonthEnd = LocalDate.now().plusMonths(1).with(TemporalAdjusters.lastDayOfMonth());
|
||||
long timestampEnd = getTimestamp(startOfNextMonthEnd.atTime(23, 59, 59, 999_000_000));
|
||||
nextMonths.add(timestampEnd);
|
||||
return nextMonths;
|
||||
}
|
||||
case "LAST_SEVEN" -> {
|
||||
List<Long> lastSevens = new ArrayList<>();
|
||||
LocalDate startOfLastSevenDays = LocalDate.now().minusDays(7);
|
||||
long timestamp = getTimestamp(startOfLastSevenDays.atStartOfDay());
|
||||
lastSevens.add(timestamp);
|
||||
long timestampEnd = getTimestamp(LocalDate.now().atStartOfDay());
|
||||
lastSevens.add(timestampEnd);
|
||||
return lastSevens;
|
||||
}
|
||||
case "SEVEN" -> {
|
||||
List<Long> sevens = new ArrayList<>();
|
||||
LocalDate startOfNextSevenDays = LocalDate.now().plusDays(6);
|
||||
long timestamp = getTimestamp(LocalDate.now().atStartOfDay());
|
||||
sevens.add(timestamp);
|
||||
long timestampEnd = getTimestamp(startOfNextSevenDays.atTime(23, 59, 59, 999_000_000));
|
||||
sevens.add(timestampEnd);
|
||||
return sevens;
|
||||
}
|
||||
case "THIRTY" -> {
|
||||
List<Long> thirty = new ArrayList<>();
|
||||
LocalDate startOfNextThirtyDays = LocalDate.now().plusDays(29);
|
||||
long timestamp = getTimestamp(LocalDate.now().atStartOfDay());
|
||||
thirty.add(timestamp);
|
||||
long timestampEnd = getTimestamp(startOfNextThirtyDays.atTime(23, 59, 59, 999_000_000));
|
||||
thirty.add(timestampEnd);
|
||||
return thirty;
|
||||
}
|
||||
case "LAST_THIRTY" -> {
|
||||
List<Long> lastThirty = new ArrayList<>();
|
||||
LocalDate startOfLastThirtyDays = LocalDate.now().minusDays(30);
|
||||
long timestamp = getTimestamp(startOfLastThirtyDays.atStartOfDay());
|
||||
lastThirty.add(timestamp);
|
||||
long timestampEnd = getTimestamp(LocalDate.now().atStartOfDay());
|
||||
lastThirty.add(timestampEnd);
|
||||
return lastThirty;
|
||||
}
|
||||
case "SIXTY" -> {
|
||||
List<Long> sixty = new ArrayList<>();
|
||||
LocalDate startOfNextSixtyDays = LocalDate.now().plusDays(59);
|
||||
long timestamp = getTimestamp(LocalDate.now().atStartOfDay());
|
||||
sixty.add(timestamp);
|
||||
long timestampEnd = getTimestamp(startOfNextSixtyDays.atTime(23, 59, 59, 999_000_000));
|
||||
sixty.add(timestampEnd);
|
||||
return sixty;
|
||||
}
|
||||
case "LAST_SIXTY" -> {
|
||||
List<Long> lastSixty = new ArrayList<>();
|
||||
LocalDate startOfLastSixtyDays = LocalDate.now().minusDays(60);
|
||||
long timestamp = getTimestamp(startOfLastSixtyDays.atStartOfDay());
|
||||
lastSixty.add(timestamp);
|
||||
long timestampEnd = getTimestamp(LocalDate.now().atStartOfDay());
|
||||
lastSixty.add(timestampEnd);
|
||||
return lastSixty;
|
||||
}
|
||||
//本季度
|
||||
case "QUARTER" -> {
|
||||
List<Long> quarters = new ArrayList<>();
|
||||
LocalDate now = LocalDate.now();
|
||||
int currentMonth = now.getMonthValue();
|
||||
int startMonth = (currentMonth - 1) / 3 * 3 + 1;
|
||||
LocalDate startOfQuarter = LocalDate.of(now.getYear(), startMonth, 1);
|
||||
long timestamp = getTimestamp(startOfQuarter.atStartOfDay());
|
||||
quarters.add(timestamp);
|
||||
LocalDate endOfQuarter = startOfQuarter.plusMonths(2).with(TemporalAdjusters.lastDayOfMonth());
|
||||
long timestampEnd = getTimestamp(endOfQuarter.atTime(23, 59, 59, 999_000_000));
|
||||
quarters.add(timestampEnd);
|
||||
return quarters;
|
||||
}
|
||||
//上季度
|
||||
case "LAST_QUARTER" -> {
|
||||
List<Long> lastQuarters = new ArrayList<>();
|
||||
LocalDate now = LocalDate.now();
|
||||
int currentMonth = now.getMonthValue();
|
||||
int startMonth = (currentMonth - 1) / 3 * 3 + 1;
|
||||
LocalDate startOfLastQuarter = LocalDate.of(now.getYear(), startMonth, 1).minusMonths(3);
|
||||
long timestamp = getTimestamp(startOfLastQuarter.atStartOfDay());
|
||||
lastQuarters.add(timestamp);
|
||||
LocalDate endOfLastQuarter = startOfLastQuarter.plusMonths(2).with(TemporalAdjusters.lastDayOfMonth());
|
||||
long timestampEnd = getTimestamp(endOfLastQuarter.atTime(23, 59, 59, 999_000_000));
|
||||
lastQuarters.add(timestampEnd);
|
||||
return lastQuarters;
|
||||
}
|
||||
//下季度
|
||||
case "NEXT_QUARTER" -> {
|
||||
List<Long> nextQuarters = new ArrayList<>();
|
||||
LocalDate now = LocalDate.now();
|
||||
int currentMonth = now.getMonthValue();
|
||||
int startMonth = (currentMonth - 1) / 3 * 3 + 1;
|
||||
LocalDate startOfNextQuarter = LocalDate.of(now.getYear(), startMonth, 1).plusMonths(3);
|
||||
long timestamp = getTimestamp(startOfNextQuarter.atStartOfDay());
|
||||
nextQuarters.add(timestamp);
|
||||
LocalDate endOfNextQuarter = startOfNextQuarter.plusMonths(2).with(TemporalAdjusters.lastDayOfMonth());
|
||||
long timestampEnd = getTimestamp(endOfNextQuarter.atTime(23, 59, 59, 999_000_000));
|
||||
nextQuarters.add(timestampEnd);
|
||||
return nextQuarters;
|
||||
}
|
||||
//本年度
|
||||
case "YEAR" -> {
|
||||
List<Long> years = new ArrayList<>();
|
||||
LocalDate startOfYear = LocalDate.now().withDayOfYear(1);
|
||||
long timestamp = getTimestamp(startOfYear.atStartOfDay());
|
||||
years.add(timestamp);
|
||||
LocalDate now = LocalDate.now().with(TemporalAdjusters.lastDayOfYear());
|
||||
long timestampEnd = getTimestamp(now.atTime(23, 59, 59, 999_000_000));
|
||||
years.add(timestampEnd);
|
||||
return years;
|
||||
}
|
||||
//上年度
|
||||
case "LAST_YEAR" -> {
|
||||
List<Long> lastYears = new ArrayList<>();
|
||||
LocalDate startOfLastYear = LocalDate.now().minusYears(1).withDayOfYear(1);
|
||||
long timestamp = getTimestamp(startOfLastYear.atStartOfDay());
|
||||
lastYears.add(timestamp);
|
||||
LocalDate startOfLastYearEnd = LocalDate.now().minusYears(1).with(TemporalAdjusters.lastDayOfYear());
|
||||
long timestampEnd = getTimestamp(startOfLastYearEnd.atTime(23, 59, 59, 999_000_000));
|
||||
lastYears.add(timestampEnd);
|
||||
return lastYears;
|
||||
}
|
||||
//下年度
|
||||
case "NEXT_YEAR" -> {
|
||||
List<Long> nextYears = new ArrayList<>();
|
||||
LocalDate startOfNextYear = LocalDate.now().plusYears(1).withDayOfYear(1);
|
||||
long timestamp = getTimestamp(startOfNextYear.atStartOfDay());
|
||||
nextYears.add(timestamp);
|
||||
LocalDate startOfNextYearEnd = LocalDate.now().plusYears(1).with(TemporalAdjusters.lastDayOfYear());
|
||||
long timestampEnd = getTimestamp(startOfNextYearEnd.atTime(23, 59, 59, 999_000_000));
|
||||
nextYears.add(timestampEnd);
|
||||
return nextYears;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
String dateValue = split[1];
|
||||
String dateUnit = split[2];
|
||||
int dateNumber = Integer.parseInt(dateValue);
|
||||
switch (dateUnit) {
|
||||
case "BEFORE_DAY" -> {
|
||||
LocalDateTime startOfLastDays = LocalDateTime.now().minusDays(dateNumber);
|
||||
return getTimestamp(startOfLastDays);
|
||||
}
|
||||
case "AFTER_DAY" -> {
|
||||
LocalDateTime startOfNextDays = LocalDateTime.now().plusDays(dateNumber);
|
||||
return getTimestamp(startOfNextDays);
|
||||
}
|
||||
case "BEFORE_WEEK" -> {
|
||||
LocalDateTime startOfLastWeeks = LocalDateTime.now().minusDays(dateNumber * 7L);
|
||||
return getTimestamp(startOfLastWeeks);
|
||||
}
|
||||
case "AFTER_WEEK" -> {
|
||||
LocalDateTime startOfNextWeeks = LocalDateTime.now().plusDays(dateNumber * 7L);
|
||||
return getTimestamp(startOfNextWeeks);
|
||||
}
|
||||
case "BEFORE_MONTH" -> {
|
||||
LocalDateTime startOfLastMonths = LocalDateTime.now().minusMonths(dateNumber);
|
||||
return getTimestamp(startOfLastMonths);
|
||||
}
|
||||
case "AFTER_MONTH" -> {
|
||||
LocalDateTime startOfNextMonths = LocalDateTime.now().plusMonths(dateNumber);
|
||||
return getTimestamp(startOfNextMonths);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public String getCombineOperator() {
|
||||
if (Strings.CI.equals(operator, CombineConditionOperator.DYNAMICS.name())) {
|
||||
String strValue = (String) value;
|
||||
String[] split = strValue.split(",");
|
||||
if (split.length == 1) {
|
||||
return CombineConditionOperator.BETWEEN.name();
|
||||
} else {
|
||||
String dateUnit = split[2];
|
||||
switch (dateUnit) {
|
||||
case "BEFORE_DAY", "BEFORE_WEEK", "BEFORE_MONTH" -> {
|
||||
return CombineConditionOperator.LT.name();
|
||||
}
|
||||
case "AFTER_DAY", "AFTER_WEEK", "AFTER_MONTH" -> {
|
||||
return CombineConditionOperator.GT.name();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return operator;
|
||||
}
|
||||
|
||||
|
||||
private long getTimestamp(LocalDateTime today) {
|
||||
// 使用系统默认时区
|
||||
ZonedDateTime zonedEndOfDay = today.atZone(ZoneId.systemDefault());
|
||||
// 转为时间戳(毫秒)
|
||||
return zonedEndOfDay.toInstant().toEpochMilli();
|
||||
}
|
||||
|
||||
/**
|
||||
* 枚举:组合条件操作符,定义了各种可能的查询操作符。
|
||||
*/
|
||||
public enum CombineConditionOperator {
|
||||
/**
|
||||
* 动态
|
||||
*/
|
||||
DYNAMICS,
|
||||
/**
|
||||
* 属于某个集合
|
||||
*/
|
||||
IN,
|
||||
|
||||
/**
|
||||
* 不属于某个集合
|
||||
*/
|
||||
NOT_IN,
|
||||
|
||||
/**
|
||||
* 区间操作
|
||||
*/
|
||||
BETWEEN,
|
||||
|
||||
/**
|
||||
* 大于
|
||||
*/
|
||||
GT,
|
||||
|
||||
/**
|
||||
* 小于
|
||||
*/
|
||||
LT,
|
||||
|
||||
/**
|
||||
* 大于等于
|
||||
*/
|
||||
GE,
|
||||
|
||||
/**
|
||||
* 小于等于
|
||||
*/
|
||||
LE,
|
||||
|
||||
/**
|
||||
* 数量大于
|
||||
*/
|
||||
COUNT_GT,
|
||||
|
||||
/**
|
||||
* 数量小于
|
||||
*/
|
||||
COUNT_LT,
|
||||
|
||||
/**
|
||||
* 等于
|
||||
*/
|
||||
EQUALS,
|
||||
|
||||
/**
|
||||
* 不等于
|
||||
*/
|
||||
NOT_EQUALS,
|
||||
|
||||
/**
|
||||
* 包含
|
||||
*/
|
||||
CONTAINS,
|
||||
|
||||
/**
|
||||
* 不包含
|
||||
*/
|
||||
NOT_CONTAINS,
|
||||
|
||||
/**
|
||||
* 为空
|
||||
*/
|
||||
EMPTY,
|
||||
|
||||
/**
|
||||
* 不为空
|
||||
*/
|
||||
NOT_EMPTY,
|
||||
/**
|
||||
* 不等于原值(用户审批时的条件判断)
|
||||
*/
|
||||
NOT_EQUAL_ORIGINAL
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package cn.cordys.common.dto.condition;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 表示组合条件,用于支持复杂的过滤和查询逻辑。
|
||||
* 包含字段名、操作符和期望值等信息。
|
||||
*/
|
||||
@Data
|
||||
public class FilterDBCondition extends FilterCondition {
|
||||
|
||||
@Schema(description = "是否是自定义字段")
|
||||
private Boolean customField = false;
|
||||
|
||||
@Schema(description = "是否是大字段")
|
||||
private Boolean blob = false;
|
||||
|
||||
@Schema(description = "是否是显示字段")
|
||||
private Boolean refFiled = false;
|
||||
|
||||
@Schema(description = "显示字段的主字段是否是自定义字段")
|
||||
private Boolean refMainCustomField = false;
|
||||
|
||||
@Schema(description = "显示字段的主字段名称或ID")
|
||||
private String refMainFieldName;
|
||||
|
||||
@Schema(description = "显示字段的主表名")
|
||||
private String refMainTableName;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.cordys.common.dto.stage;
|
||||
|
||||
import cn.cordys.common.domain.BaseModuleFieldValue;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class CirculationFieldValue extends BaseModuleFieldValue {
|
||||
|
||||
@Schema(description = "是否必填")
|
||||
private Boolean required;
|
||||
|
||||
@Schema(description = "默认值类型")
|
||||
private String valueType;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package cn.cordys.common.dto.stage;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class CirculationSetting {
|
||||
|
||||
@Schema(description = "源id")
|
||||
private String originId;
|
||||
|
||||
@Schema(description = "源id对应的行目标ids")
|
||||
private List<Target> targets;
|
||||
|
||||
@Schema(description = "模块类型(order-订单/contract-合同)")
|
||||
private String moduleType;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package cn.cordys.common.dto.stage;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class StageAddRequest {
|
||||
|
||||
@Schema(description = "值")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "类型")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "添加的位置(取值:-1,,1。 -1:源节点之前,1:源节点之后)", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private int dropPosition;
|
||||
|
||||
@Schema(description = "源节点")
|
||||
private String targetId;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package cn.cordys.common.dto.stage;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class StageAdvancedConfigRequest {
|
||||
|
||||
@Schema(description = "流转配置类型")
|
||||
private String circulationType;
|
||||
|
||||
@Schema(description = "高级流转设置")
|
||||
private List<CirculationSetting> circulationSettings;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package cn.cordys.common.dto.stage;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class StageConfigResponse {
|
||||
|
||||
@Schema(description = "ID")
|
||||
private String id;
|
||||
|
||||
@Schema(description = "状态")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "类型")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "进行中回退设置")
|
||||
private Boolean afootRollBack;
|
||||
|
||||
@Schema(description = "完结回退设置")
|
||||
private Boolean endRollBack;
|
||||
|
||||
@Schema(description = "顺序")
|
||||
private Long pos;
|
||||
|
||||
@Schema(description = "当前阶段是否存在数据")
|
||||
private Boolean stageHasData = false;
|
||||
|
||||
@Schema(description = "流转类型")
|
||||
private String circulationType;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package cn.cordys.common.dto.stage;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class StageConfigsResponse {
|
||||
|
||||
@Schema(description = "订单状态流配置列表")
|
||||
List<StageConfigResponse> stageConfigList;
|
||||
|
||||
@Schema(description = "进行中回退设置")
|
||||
private Boolean afootRollBack = true;
|
||||
|
||||
@Schema(description = "完结回退设置")
|
||||
private Boolean endRollBack = false;
|
||||
|
||||
@Schema(description = "流转配置类型")
|
||||
private String circulationType;
|
||||
|
||||
@Schema(description = "高级流转设置")
|
||||
private List<CirculationSetting> advancedConfigs;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package cn.cordys.common.dto.stage;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class StageRollBackRequest {
|
||||
|
||||
@Schema(description = "进行中回退设置")
|
||||
private Boolean afootRollBack;
|
||||
|
||||
@Schema(description = "完结回退设置")
|
||||
private Boolean endRollBack;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package cn.cordys.common.dto.stage;
|
||||
|
||||
import cn.cordys.common.domain.BaseModuleFieldValue;
|
||||
import cn.cordys.crm.system.dto.request.NodeMoveRequest;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class StageSortRequest extends NodeMoveRequest {
|
||||
|
||||
@NotBlank
|
||||
@Schema(description = "阶段", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String stage;
|
||||
|
||||
@Schema(description = "更新字段")
|
||||
private List<BaseModuleFieldValue> fields;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package cn.cordys.common.dto.stage;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class StageUpdateRequest {
|
||||
|
||||
@Schema(description = "id")
|
||||
private String id;
|
||||
|
||||
@Schema(description = "状态名称")
|
||||
private String name;
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user