1
0

2 Achegas c2ffe3f947 ... 8128633b4f

Autor SHA1 Mensaxe Data
  any50 8128633b4f 合并 hai 1 mes
  any50 76c19d8721 hh hai 1 mes
Modificáronse 63 ficheiros con 7652 adicións e 14 borrados
  1. 26 14
      .gitignore
  2. 21 0
      base/nckd-base-common/build.gradle
  3. 12 0
      base/nckd-base-helper/build.gradle
  4. 176 0
      build.gradle
  5. 44 0
      config.gradle
  6. 3 0
      cosmic.properties
  7. BIN=BIN
      docs/images/code1.png
  8. BIN=BIN
      docs/images/code2.png
  9. BIN=BIN
      docs/images/cosmic-studio-qrcode.png
  10. BIN=BIN
      docs/images/eclipse-gradle.png
  11. BIN=BIN
      docs/images/idea-gradle.png
  12. BIN=BIN
      docs/images/kddt-qrcode.png
  13. 26 0
      gradle.properties
  14. 240 0
      gradlew
  15. 24 0
      nckd-cosmic-debug/build.gradle
  16. 47 0
      nckd-cosmic-debug/src/main/java/nckd/cosmic/debug/DebugApplication.java
  17. 35 0
      nckd-cosmic-debug/src/main/java/nckd/cosmic/debug/DebugClApplication.java
  18. 456 0
      nckd-cosmic-debug/src/main/java/nckd/cosmic/debug/Launcher.java
  19. 181 0
      nckd-cosmic-debug/src/main/java/nckd/cosmic/debug/LauncherByCl.java
  20. 63 0
      nckd-cosmic-debug/src/main/resources/logback-kafka.xml
  21. 63 0
      nckd-cosmic-debug/src/main/resources/logback.xml
  22. 14 0
      nckd-fi/build.gradle
  23. 22 0
      nckd-fi/src/main/java/nckd/fi/er/common/AppflgConstant.java
  24. 104 0
      nckd-fi/src/main/java/nckd/fi/er/common/BaseFieldConst.java
  25. 61 0
      nckd-fi/src/main/java/nckd/fi/er/common/BillStatusEnum.java
  26. 47 0
      nckd-fi/src/main/java/nckd/fi/er/webapi/model/BaseDataModel.java
  27. 15 0
      nckd-fi/src/main/java/nckd/fi/er/webapi/model/BaseModel.java
  28. 175 0
      nckd-fi/src/main/java/nckd/fi/er/webapi/model/BdAttachmentModel.java
  29. 10 0
      nckd-fi/src/main/java/nckd/fi/er/webapi/model/BosUserContactEtModel.java
  30. 25 0
      nckd-fi/src/main/java/nckd/fi/er/webapi/model/BosUserEntityModel.java
  31. 87 0
      nckd-fi/src/main/java/nckd/fi/er/webapi/model/BosUserModel.java
  32. 10 0
      nckd-fi/src/main/java/nckd/fi/er/webapi/model/UserTypeModel.java
  33. 135 0
      nckd-fi/src/main/java/nckd/fi/er/webapi/utils/BdAttachmentUtils.java
  34. 502 0
      nckd-fi/src/main/java/nckd/fi/er/webapi/utils/DoMoConvertUtils.java
  35. 14 0
      nckd-hr/build.gradle
  36. 67 0
      nckd-hr/src/main/java/nckd/hr/hrpi/plugin/operate/SyncUserServiceEx.java
  37. 22 0
      nckd-hr/src/main/java/nckd/hr/hspm/common/AppflgConstant.java
  38. 104 0
      nckd-hr/src/main/java/nckd/hr/hspm/common/constant/BaseFieldConst.java
  39. 84 0
      nckd-hr/src/main/java/nckd/hr/hspm/common/enums/BillStatusEnums.java
  40. 734 0
      nckd-hr/src/main/java/nckd/hr/hspm/common/util/DateUtil.java
  41. 161 0
      nckd-hr/src/main/java/nckd/hr/hspm/common/util/QFilterUtils.java
  42. 12 0
      nckd-hr/src/main/java/nckd/hr/hspm/plugin/operate/SyncPersonToUserExtPlugin.java
  43. 65 0
      nckd-hr/src/main/java/nckd/hr/hspm/plugin/operate/SyncUserServiceEx.java
  44. 794 0
      nckd-hr/src/main/java/nckd/yanye/hr/report/gzbb/AttachFileUtils.java
  45. 437 0
      nckd-hr/src/main/java/nckd/yanye/hr/report/gzbb/PushSjcjDataUtils.java
  46. 105 0
      nckd-hr/src/main/java/nckd/yanye/hr/report/gzbb/SjcjCheckFormListPlugin.java
  47. 187 0
      nckd-hr/src/main/java/nckd/yanye/hr/report/gzbb/SjcjCheckFormPlugin.java
  48. 33 0
      nckd-hr/src/main/java/nckd/yanye/hr/report/gzbb/SjtbBackWorkflowPlugin.java
  49. 192 0
      nckd-hr/src/main/java/nckd/yanye/hr/report/gzbb/SjtbCheckFormListPlugin.java
  50. 109 0
      nckd-hr/src/main/java/nckd/yanye/hr/report/gzbb/SjtbCheckFormPlugin.java
  51. 33 0
      nckd-hr/src/main/java/nckd/yanye/hr/report/gzbb/SjtbSubmitWorkflowPlugin.java
  52. 14 0
      nckd-pur/build.gradle
  53. 105 0
      nckd-pur/src/main/java/nckd/pur/scp/business/SendMsgToWechatOA.java
  54. 22 0
      nckd-pur/src/main/java/nckd/pur/scp/common/AppflgConstant.java
  55. 736 0
      nckd-pur/src/main/java/nckd/pur/scp/common/DateUtil.java
  56. 104 0
      nckd-pur/src/main/java/nckd/pur/scp/common/constant/BaseFieldConst.java
  57. 433 0
      nckd-pur/src/main/java/nckd/pur/scp/common/utils/FormUtils.java
  58. 122 0
      nckd-pur/src/main/java/nckd/pur/scp/mservice/SendMsgToWeChatOA.java
  59. 36 0
      nckd-pur/src/main/java/nckd/pur/scp/plugin/form/PurApplyBillPlugin.java
  60. 59 0
      nckd-pur/src/main/java/nckd/pur/scp/plugin/operate/SaloutStockOpPlugin.java
  61. 63 0
      nckd-pur/src/main/java/nckd/pur/scp/plugin/operate/SendMsgToPurPersonExt.java
  62. 144 0
      readme.md
  63. 37 0
      settings.gradle

+ 26 - 14
.gitignore

@@ -1,14 +1,26 @@
-# ---> Java
-*.class
-
-# Mobile Tools for Java (J2ME)
-.mtj.tmp/
-
-# Package Files #
-*.jar
-*.war
-*.ear
-
-# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
-hs_err_pid*
-
+.mvn
+.metadata
+.class
+.log
+*.log
+.gradle
+.project
+.settings
+.classpath
+bin
+target
+logs
+.idea
+*.imlgit
+out
+pinyin
+assembly
+**/*.bak
+.DS_Store
+**/build/**
+**/gradle/**
+gradlew.*
+**/*.class
+**/*.jar
+**/.gradle/**
+**/*.DS_Store

+ 21 - 0
base/nckd-base-common/build.gradle

@@ -0,0 +1,21 @@
+/*
+ * This is a kingdee cosmic template project that is automatically generated by the Kingdee cosmic development assistant plugin. 
+ * If there are any issues during the use process, you can provide feedback to the kingdee developer community website.
+ * Website: https://developer.kingdee.com/developer?productLineId=29
+ * Author: liebin.zheng
+ * Generate Date: 2025-11-21 10:24:13
+ */
+
+apply from: '../../config.gradle'
+
+def bos = ext.path.bos	
+def trd = ext.path.trd
+def cus = ext.path.cus
+def biz = ext.path.biz
+
+dependencies {
+	api fileTree(dir: bos, include: '*.jar')
+	api fileTree(dir: trd, include: '*.jar')
+	api fileTree(dir: biz, include: '*.jar')
+	runtimeOnly fileTree(dir: cus, include: '*.jar')
+}

+ 12 - 0
base/nckd-base-helper/build.gradle

@@ -0,0 +1,12 @@
+/*
+ * This is a kingdee cosmic template project that is automatically generated by the Kingdee cosmic development assistant plugin. 
+ * If there are any issues during the use process, you can provide feedback to the kingdee developer community website.
+ * Website: https://developer.kingdee.com/developer?productLineId=29
+ * Author: liebin.zheng
+ * Generate Date: 2025-11-21 10:24:13
+ */
+
+dependencies {
+	api project(':nckd-base-common')
+} 
+

+ 176 - 0
build.gradle

@@ -0,0 +1,176 @@
+/**
+ * This is a kingdee cosmic template project that is automatically generated by the Kingdee cosmic development assistant plugin. 
+ * If there are any issues during the use process, you can provide feedback to the kingdee developer community website.
+ * Website: https://developer.kingdee.com/developer?productLineId=29
+ * Author: liebin.zheng
+ * Generate Date: 2025-11-21 10:24:13
+ */
+
+plugins {
+	id 'java'
+	//https://docs.gradle.org/current/userguide/java_library_plugin.html#java_library_plugin
+    id 'java-library'
+    //id 'maven-publish'
+    //https://docs.gradle.org/current/userguide/idea_plugin.html
+    //id 'idea'
+    //https://docs.gradle.org/current/userguide/eclipse_plugin.html
+    //id 'eclipse'
+    //id "org.sonarqube" version "3.5.0.2730"
+}
+
+apply from: 'config.gradle'
+
+def bos = ext.path.bos	
+def trd = ext.path.trd
+def cus = ext.path.cus
+def biz = ext.path.biz
+def outputdir = ext.path.outputdir
+
+//所有工程共用的配置
+allprojects {
+	
+	apply plugin: 'java'
+	apply plugin: 'maven-publish'
+	apply plugin: 'java-library'
+	apply plugin: 'eclipse'
+	apply plugin: 'idea'
+
+	repositories{
+		mavenLocal()
+		maven{ url'https://maven.aliyun.com/repository/public/'}
+		maven{ url'https://maven.aliyun.com/repository/gradle-plugin'}
+		mavenCentral()
+		//maven { url 'https://repo.gradle.org/gradle/libs-releases' }
+		//gradlePluginPortal()
+	}
+	
+	group = System.getProperty('groupId')
+	version = System.getProperty('version')
+	
+	def jdk_version = System.getProperty('jdk.version')
+	sourceCompatibility = jdk_version
+	targetCompatibility = jdk_version
+	
+	tasks.withType(JavaCompile) {  
+		options.encoding = "UTF-8" 
+	}
+
+	dependencies {
+		
+		//implementation 'cn.hutool:hutool-all:5.8.20'
+		//implementation 'ch.qos.logback:logback-classic:1.2.12'
+		//implementation 'org.slf4j:log4j-over-slf4j:1.7.36'
+		//implementation 'org.apache.logging.log4j:log4j-to-slf4j:2.20.0' 
+		//testImplementation platform('org.junit:junit-bom:5.9.1')
+    	//testImplementation 'org.junit.jupiter:junit-jupiter'
+    	
+	}
+	
+	test {
+	    useJUnitPlatform()
+	}
+	
+	java {
+	    withSourcesJar()
+	    //withJavadocJar()
+	}
+	
+	Date now = new Date()
+	String date = now.format('yyyy-MM-dd HH:mm:ss')
+	String buildNum = now.format('yyyyMMddHHmmssSSS')
+	
+	String gitCommitShortHash = ''
+	String gitBranch = ''
+	try{
+		gitCommitShortHash = 'git log -1 --pretty=%H'.execute([], project.rootDir).text.trim()
+		gitBranch = 'git branch --show-current'.execute([], project.rootDir).text.trim()
+	} catch(Exception e){
+		println "warning: the commandline tools of git was not installed!"
+	}
+	
+	jar {
+	    manifest {
+	    	attributes 'Group-Name': project.group
+	    	attributes 'Project-Name': project.name
+	    	attributes 'Bundle-Version': project.version
+	    	attributes 'Build-Num': buildNum
+	        attributes 'Build-Date': date
+	    	attributes 'Build-Jdk': System.getProperty('java.version')
+	    	attributes 'Built-By': 'Kingdee Cosmic Developer Tools'
+	    	attributes 'Build-Tool': 'Gradle ' + project.gradle.gradleVersion
+	        attributes 'Git-Commit-Hash': gitCommitShortHash
+	    	attributes 'Git-Branch': gitBranch
+	        attributes 'Cloud-Name': ''
+	        attributes 'App-Name': ''
+	        attributes 'Jar-Id': ''
+	        attributes 'Build-Image': ''
+	    }
+	}
+	
+}
+
+//所有子工程共用的配置
+subprojects {
+	
+	dependencies {
+		
+	}
+	
+	//生成本工程jar包并拷贝到outputdir目录
+	task buildJar(type: Copy, dependsOn: build){
+		group 'build'
+		description '生成本工程jar包并拷贝到outputdir目录'
+		from 'build/libs'
+		into outputdir
+		exclude '*sources.jar','*javadoc.jar','*cosmic-debug*.jar'
+		processResources{
+			from('src/main/java'){
+				include '**/*.properties'
+			}
+		}
+	}
+	
+	//生成本工程jar包并拷贝到拷贝到cus目录
+	task deployJar(type: Copy, dependsOn: build){
+	 	group 'build'
+		description '生成本工程jar包并拷贝到拷贝到cus目录'
+		from 'build/libs'
+		into cus
+		exclude '*sources.jar','*javadoc.jar','*cosmic-debug*.jar'
+		processResources{
+			from('src/main/java'){
+				include '**/*.properties'
+			}
+		}
+	 }
+	 
+	test.ignoreFailures true
+
+	task generate {
+		doLast {
+			def buildFile = project.buildscript.sourceFile.toString()
+			println("buildFile:" + buildFile)
+			def fileSrc = buildFile.replace("build.gradle", "").concat("sbt-nm-webapi/src/main/java/").concat("sbt/nm/webapi/model")
+			println("fileSrc:" + fileSrc)
+
+			// 应用编码、页面编码、定义类名称、数据中心ID、租户ID
+			def appName = 'sbt_nm_launch'
+			def entityId = 'sbt_nm_launchappraisal'
+			def className = 'launchappraisal'
+			def datacenterId = '1905905108789493760'
+			def tenantCode = 'sbt-test-tenant'
+
+			// 处理类请求地址
+			def req = new URL('http://127.0.0.1:8080/ierp/xn/auth/develop/modelclass/generate.do?appId=' + appName + '&entityId='
+					+ entityId + '&className=' + className + "&fileSrc=" + fileSrc + "&datacenterId=" + datacenterId+ "&tenantCode=" + tenantCode).openConnection()
+			println("URL:" + req)
+			req.setRequestMethod("GET")
+			req.setRequestProperty("Content-Type", "application/json; charset=UTF-8")
+			req.setDoOutput(true)
+			def text = req.getInputStream().getText()
+			println("Response:" + text)
+		}
+	}
+	
+}
+

+ 44 - 0
config.gradle

@@ -0,0 +1,44 @@
+/**
+ * This is a kingdee cosmic template project that is automatically generated by the Kingdee cosmic development assistant plugin. 
+ * If there are any issues during the use process, you can provide feedback to the kingdee developer community website.
+ * Website: https://developer.kingdee.com/developer?productLineId=29
+ * Author: liebin.zheng
+ * Generate Date: 2025-11-21 10:24:13
+ */
+def cosmic_libs_path = System.getProperty('cosmic_libs_path')
+def cosmic_home = null
+
+if(cosmic_libs_path == null){
+	println "NO 'cosmic_libs_path' property was setted in gradle.properties. "
+	println "Try to find the 'cosmic_home' property in gradle.properties."
+	cosmic_home = System.getProperty('cosmic_home')
+}
+
+if(cosmic_home == null){
+	println "NO 'cosmic_home' property was setted in gradle.properties. "
+	println "Try to find the 'COSMIC_HOME' property in System environment."
+	cosmic_home = System.getenv('COSMIC_HOME')
+}
+
+if(cosmic_home != null){
+	cosmic_libs_path = "${cosmic_home}/mservice-cosmic/lib"
+}
+
+if(cosmic_libs_path == null){
+	throw new RuntimeException("Neither 'COSMIC_LIBS_PATH' was setted in System environment, nor 'cosmic_libs_path' property was setted in gradle.properties.")
+}
+
+
+println "The 'cosmic_libs_path' is: '${cosmic_libs_path}'."
+
+ext	{
+
+	path = [
+		trd : "${cosmic_libs_path}/trd",
+		bos : "${cosmic_libs_path}/bos",
+		biz : "${cosmic_libs_path}/biz",
+		cus : "${cosmic_libs_path}/cus",
+		outputdir : "${cosmic_libs_path}/outputdir"
+	]
+
+}

+ 3 - 0
cosmic.properties

@@ -0,0 +1,3 @@
+#\u8BE5\u8D44\u6E90\u5730\u5740\u63D0\u4F9B\u4E86\u82CD\u7A79\u73AF\u5883\u6240\u9700\u7684\u5F00\u53D1\u8D44\u6E90\u5305\uFF0C\u7528\u4E8E\u4E0B\u8F7D\u6216\u66F4\u65B0\u82CD\u7A79\u5F00\u53D1\u8D44\u6E90\u5305\u5230\u82CD\u7A79\u8D44\u6E90\u76EE\u5F55\u4E2D\u3002\u8BE5\u5730\u5740\u4E00\u822C\u7531\u82CD\u7A79MC\u670D\u52A1\u6216\u82CD\u7A79\u5F00\u53D1\u8005\u5DE5\u5177\u5BF9\u5916\u5F00\u653E\uFF0C\u5177\u4F53\u8D44\u6E90\u5305\u4E3A\uFF1Acosmic.zip\u3001webapp.zip\uFF0C\u5171\u7EA62G\u3002\u5730\u5740\u683C\u5F0F\u4E3A\uFF1Ahttp://\u82CD\u7A79MC\u670D\u52A1IP:\u7AEF\u53E3/appstore/dev_env\u6216http://\u82CD\u7A79\u5F00\u53D1\u8005\u5DE5\u5177\u670D\u52A1IP:268/studio/environment/download/{\u73AF\u5883\u5E8F\u53F7}\u3002
+#Mon Nov 24 17:08:43 CST 2025
+MCServerURL=http\://111.74.9.60\:58023/appstore/cosmic

BIN=BIN
docs/images/code1.png


BIN=BIN
docs/images/code2.png


BIN=BIN
docs/images/cosmic-studio-qrcode.png


BIN=BIN
docs/images/eclipse-gradle.png


BIN=BIN
docs/images/idea-gradle.png


BIN=BIN
docs/images/kddt-qrcode.png


+ 26 - 0
gradle.properties

@@ -0,0 +1,26 @@
+### ----------------------------
+### This is a kingdee cosmic template project that is automatically generated by the Kingdee cosmic development assistant plugin. 
+### If there are any issues during the use process, you can provide feedback to the kingdee developer community website.
+### Website: https://developer.kingdee.com/developer?productLineId=29
+### Author: liebin.zheng
+### Generate Date: 2025-11-21 10:24:13
+### ----------------------------
+systemProp.kddt_version=2.1.5
+systemProp.template_type=cloud
+systemProp.groupId=nckd.cosmic
+systemProp.artifactId=nckd-cosmic
+systemProp.version=1.0.0
+systemProp.jdk.version=1.8
+systemProp.developer_flag=nckd
+systemProp.project_dir=D:/ByWork/IdeaWorkSpace/jxcl
+systemProp.cosmic_home=D:/ByWork/IdeaProject
+#systemProp.cosmic_home=C:/Users/any50/cosmic/home
+#systemProp.cosmic_libs_path=C:/Users/any50/cosmic/home/mservice-cosmic/lib
+#systemProp.cosmic_static_path=C:/Users/any50/cosmic/home/static-file-service
+#systemProp.res_url=http://127.0.0.1:268/studio/environment/download/6
+#systemProp.zk_url=127.0.0.1:2181
+#systemProp.mc_url=http://127.0.0.1:8091/
+org.gradle.parallel=true
+org.gradle.daemon=true
+org.gradle.caching=true
+org.gradle.jvmargs=-Xms256m -Xmx1024m -XX:MaxMetaspaceSize=128m

+ 240 - 0
gradlew

@@ -0,0 +1,240 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+#   Gradle start up script for POSIX generated by Gradle.
+#
+#   Important for running:
+#
+#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+#       noncompliant, but you have some other compliant shell such as ksh or
+#       bash, then to run this script, type that shell name before the whole
+#       command line, like:
+#
+#           ksh Gradle
+#
+#       Busybox and similar reduced shells will NOT work, because this script
+#       requires all of these POSIX shell features:
+#         * functions;
+#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+#         * compound commands having a testable exit status, especially «case»;
+#         * various built-in commands including «command», «set», and «ulimit».
+#
+#   Important for patching:
+#
+#   (2) This script targets any POSIX shell, so it avoids extensions provided
+#       by Bash, Ksh, etc; in particular arrays are avoided.
+#
+#       The "traditional" practice of packing multiple parameters into a
+#       space-separated string is a well documented source of bugs and security
+#       problems, so this is (mostly) avoided, by progressively accumulating
+#       options in "$@", and eventually passing that to Java.
+#
+#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+#       see the in-line comments for details.
+#
+#       There are tweaks for specific operating systems such as AIX, CygWin,
+#       Darwin, MinGW, and NonStop.
+#
+#   (3) This script is generated from the Groovy template
+#       https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+#       within the Gradle project.
+#
+#       You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+    APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path
+    [ -h "$app_path" ]
+do
+    ls=$( ls -ld "$app_path" )
+    link=${ls#*' -> '}
+    case $link in             #(
+      /*)   app_path=$link ;; #(
+      *)    app_path=$APP_HOME$link ;;
+    esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx1024m" "-Xms256m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+    echo "$*"
+} >&2
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in                #(
+  CYGWIN* )         cygwin=true  ;; #(
+  Darwin* )         darwin=true  ;; #(
+  MSYS* | MINGW* )  msys=true    ;; #(
+  NONSTOP* )        nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD=$JAVA_HOME/jre/sh/java
+    else
+        JAVACMD=$JAVA_HOME/bin/java
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD=java
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+    case $MAX_FD in #(
+      max*)
+        MAX_FD=$( ulimit -H -n ) ||
+            warn "Could not query maximum file descriptor limit"
+    esac
+    case $MAX_FD in  #(
+      '' | soft) :;; #(
+      *)
+        ulimit -n "$MAX_FD" ||
+            warn "Could not set maximum file descriptor limit to $MAX_FD"
+    esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+#   * args from the command line
+#   * the main class name
+#   * -classpath
+#   * -D...appname settings
+#   * --module-path (only if needed)
+#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+    APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+    CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+    JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    for arg do
+        if
+            case $arg in                                #(
+              -*)   false ;;                            # don't mess with options #(
+              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath
+                    [ -e "$t" ] ;;                      #(
+              *)    false ;;
+            esac
+        then
+            arg=$( cygpath --path --ignore --mixed "$arg" )
+        fi
+        # Roll the args list around exactly as many times as the number of
+        # args, so each arg winds up back in the position where it started, but
+        # possibly modified.
+        #
+        # NB: a `for` loop captures its iteration list before it begins, so
+        # changing the positional parameters here affects neither the number of
+        # iterations, nor the values presented in `arg`.
+        shift                   # remove old arg
+        set -- "$@" "$arg"      # push replacement arg
+    done
+fi
+
+# Collect all arguments for the java command;
+#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+#     shell script including quotes and variable substitutions, so put them in
+#     double quotes to make sure that they get re-expanded; and
+#   * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+        "-Dorg.gradle.appname=$APP_BASE_NAME" \
+        -classpath "$CLASSPATH" \
+        org.gradle.wrapper.GradleWrapperMain \
+        "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+    die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+#   readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+#   set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+        printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+        xargs -n1 |
+        sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+        tr '\n' ' '
+    )" '"$@"'
+
+exec "$JAVACMD" "$@"

+ 24 - 0
nckd-cosmic-debug/build.gradle

@@ -0,0 +1,24 @@
+/*
+ * This is a kingdee cosmic template project that is automatically generated by the Kingdee cosmic development assistant plugin. 
+ * If there are any issues during the use process, you can provide feedback to the kingdee developer community website.
+ * Website: https://developer.kingdee.com/developer?productLineId=29
+ * Author: liebin.zheng
+ * Generate Date: 2025-11-21 10:24:13
+ */
+
+dependencies {
+
+	// 公共基础包
+	implementation project(':nckd-base-common')
+	implementation project(':nckd-base-helper')
+//
+	// 引入fi云模块
+	implementation project(':nckd-fi')
+
+	// 引入hr云模块
+	implementation project(':nckd-hr')
+
+	// 引入pur云模块
+	implementation project(':nckd-pur')
+
+}

+ 47 - 0
nckd-cosmic-debug/src/main/java/nckd/cosmic/debug/DebugApplication.java

@@ -0,0 +1,47 @@
+/**
+ * This is a kingdee cosmic template project that is automatically generated by the Kingdee cosmic development assistant plugin. 
+ * If there are any issues during the use process, you can provide feedback to the kingdee developer community website.
+ * Website: https://developer.kingdee.com/developer?productLineId=29
+ * Author: liebin.zheng
+ * Generate Date: 2025-11-21 10:24:13
+ */
+package nckd.cosmic.debug;
+
+/**
+ * 启动本地应用程序(微服务节点)
+ */
+public class DebugApplication {
+	
+	
+
+    public static void main(String[] args) {
+    	
+//    	Thread.currentThread().setContextClassLoader(new KDSecurityClassLoader(Thread.currentThread().getContextClassLoader()));
+    	
+        Launcher cosmic = new Launcher(false);
+
+//        cosmic.setClusterNumber("cosmic");
+//        cosmic.setTenantNumber("ierp");
+        cosmic.setClusterNumber("cl-sit");
+        cosmic.setTenantNumber("cl-sit");
+        
+//        cosmic.setConfigUrl("127.0.0.1:2181?user=zk&password=******");
+//        cosmic.setConfigUrl("127.0.0.1:2182/?user=zookeeper&password=zt111222");
+        cosmic.setConfigUrl("10.0.55.10:2181?user=zookeeper&password=d@f*g:SGVsbG8==AkVeSR0FdQBE0tuAn88ekDn9cKsWEsW7FAoI/PJCuMNHa2RwYXNzd29yZA==");
+
+        
+//        cosmic.setMcServerUrl("http://127.0.0.1:8091");
+        cosmic.setMcServerUrl("http://10.0.55.10:8090");
+
+        cosmic.setWebResPath("C:/Users/any50/cosmic/home/static-file-service");
+
+//        cosmic.setFsServerUrl("127.0.0.1", 8100);
+//        cosmic.setImageServerUrl("127.0.0.1", 8100);
+        
+        //自定义本地苍穹调试服务的端口
+        cosmic.setCosmicWepPort(8881);
+
+        cosmic.start();
+        
+    }
+}

+ 35 - 0
nckd-cosmic-debug/src/main/java/nckd/cosmic/debug/DebugClApplication.java

@@ -0,0 +1,35 @@
+package nckd.cosmic.debug;
+
+/**
+ * @Author:Zt
+ * @Date:2025/11/24
+ **/
+public class DebugClApplication {
+
+    public static void main(String[] args) {
+        Launcher cosmic = new Launcher();
+        System.setProperty("diskcache.path","D:\\ByWork\\IdeaProject\\diskCache");
+
+        System.setProperty("tempfile.cachetype","disk");
+        System.setProperty("login.session.terminalLimit", "true");
+
+        System.setProperty("redismodelcache.enablelua","false");
+        cosmic.setClusterNumber("cl-sit");
+        cosmic.setTenantNumber("cl-sit");
+        cosmic.setServerIp("10.0.55.10");
+
+        cosmic.setAppName("cosmic_cl");
+        cosmic.setWebResPath("C:/Users/any50/cosmic/home/static-file-service");//需要修改
+
+        cosmic.setConfigUrl("10.0.55.10:2181","zookeeper","XTyk%%jd141");//需要修改
+
+        cosmic.setStartWithQing(false);
+        //cosmic.set("lightweightdeploy", "true");  //标注环境为轻量级
+
+        cosmic.set("redismodelcache.enablelua", "false");  //轻量级不能用lua,设为false
+        cosmic.set("mq.debug.queue.tag", "zzzzztttt_cosmic");
+        cosmic.set("lightweightdeploy.services" , "");  //设置服务为空。服务由MC启动,这里就不用启动了。
+        cosmic.start();
+    }
+
+}

+ 456 - 0
nckd-cosmic-debug/src/main/java/nckd/cosmic/debug/Launcher.java

@@ -0,0 +1,456 @@
+/**
+ * This is a kingdee cosmic template project that is automatically generated by the Kingdee cosmic development assistant plugin. 
+ * If there are any issues during the use process, you can provide feedback to the kingdee developer community website.
+ * Website: https://developer.kingdee.com/developer?productLineId=29
+ * Author: liebin.zheng
+ * Generate Date: 2025-11-21 10:24:13
+ */
+package nckd.cosmic.debug;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.UnknownHostException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import kd.bos.config.client.util.ConfigUtils;
+import kd.bos.service.bootstrap.Booter;
+
+/**
+ * cosmic服务启动器,含默认配置,如需更改请在Application中设置。
+ */
+public final class Launcher {
+	
+//	private static final Log LOG = LogbackFactory.getLog(Launcher.class);
+
+	private static final String LOCAL_IP = "127.0.0.1";
+	
+	public static String localHostName;
+	
+    private boolean setConfigUrl = false;
+
+    private int cosmicPort = 8881;
+    
+    private String cosmicUrl = "http://127.0.0.1:" + cosmicPort + "/ierp";
+    
+//    private boolean enableLightWeightDeploy = true;
+    
+    /**
+     * 是否优先使用MC服务端的配置替代本地调试模板工程的部分默认配置
+     */
+    private boolean useMcServiceConfigFirst = false;
+    
+    public Launcher() {
+        setDefault();
+    }
+    
+    /**
+     * @param useMcServiceConfigFirst 是否优先使用MC服务端的配置替代本地调试模板工程的部分默认配置
+     */
+    public Launcher(boolean useMcServiceConfigFirst) {
+    	this.useMcServiceConfigFirst = useMcServiceConfigFirst;
+        setDefault();
+    }
+
+//    public void setDefault() {
+//
+//        set("configAppName", "mservice,web");
+//        set("webmserviceinone", "true");
+//        set("file.encoding", "utf-8");
+//        set("MONITOR_HTTP_PORT", "9998");
+//        set("JMX_HTTP_PORT", "9091");
+//        set("appSplit", "false");
+//        set("tenant.code.type", "config");
+//
+//        localHostName = getLocalHostName();
+//        setClusterNumber("cosmic");
+//        setTenantNumber("ierp");
+//        setAppName("kdcosmic-" + localHostName + "-" + cosmicPort);
+//
+//        setXdbEnable(false);
+//        setSqlOut(true, true);
+//
+//        setCosmicWepPort(cosmicPort);
+//        setWebResPath(System.getenv("COSMIC_HOME") + "/static-file-service");
+//
+//        //是否优先使用MC服务端的配置替代本地调试模板工程的部分默认配置
+//		if (!useMcServiceConfigFirst) {
+//			//当本地开发且连接轻量级环境时,可使用以下默认配置
+//	        setConfigUrl("127.0.0.1:2181");
+//	        setMcServerUrl("http://127.0.0.1:8090");
+//	        setFsServerUrl("127.0.0.1", 8100);
+//	        setImageServerUrl("127.0.0.1", 8100);
+//
+////	        setEnableLightWeightDeploy(true);
+//	        setDubboHostConfig(LOCAL_IP, 28888, 30880);
+////	        set("login.type", "STANDALONE");
+////	        setMqConsumerRegister(false, null);
+//            setMqConsumerRegister(true, localHostName);
+//
+//        } else {
+//			//当需要连接项目开发环境(即非轻量级环境)时,应以MC服务器上面的配置优先
+////			setEnableLightWeightDeploy(false);
+//			setMqConsumerRegister(true, localHostName);
+//		}
+//
+//		setStartWithQing(false);
+//
+//		//是否以轻量级环境启动苍穹服务
+//        setEnableLightWeightDeploy(true);
+//
+//        //本地日志配置
+//        setLogConfig(false);
+//
+//        //Dubbo服务注册配置
+//        setDubboConfig(false, true, true);
+//
+//    }
+
+    private void setDefault() {
+        set("configAppName", "mservice,web");
+        set("webmserviceinone", "true");
+        set("file.encoding", "utf-8");
+        set("mq.consumer.register", "true");
+        set("MONITOR_HTTP_PORT", "9998");
+        set("JMX_HTTP_PORT", "9091");
+        set("dubbo.protocol.port", "28888");
+//        set("dubbo.consumer.url", "dubbo://192.168.2.125:28888");
+//        set("dubbo.consumer.url.qing", "dubbo://192.168.2.125:308801");
+        set("dubbo.consumer.url", "dubbo://10.0.55.10:28888");
+        set("dubbo.consumer.url.qing", "dubbo://10.0.55.10:30880");
+        set("dubbo.registry.register", "false");
+        set("dubbo.service.lookup.local", "true");
+        set("appSplit", "false");
+        set("tenant.code.type", "config");
+        set("JETTY_WEB_PORT", "8080");
+//        set("domain.contextUrl", "http://localhost:8080/ierp");
+        set("domain.contextUrl", "http://127.0.0.1:8080/ierp");
+        set("lightweightdeploy", "true");
+        set("redismodelcache.enablelua", "false");
+        set("lightweightdeploy.services", "");
+
+        try {
+            String logConfig = new String(Files.readAllBytes(Paths.get(getClass().getResource("log.config.xml").toURI())), "UTF-8");
+            set("log.config", logConfig);
+        } catch (Exception e) {
+            //ignore
+        }
+
+        setClusterNumber("cosmic");
+        setTenantNumber("sample");
+        setAppName("cosmic-server");
+//        setStartWithQing(true);
+        setXdbEnable(false);
+        setSqlOut(true, true);
+    }
+
+    public void start() {
+    	
+//    	LOG.info("Cosmic Service starting! Please check url: {}", getCosmicUrl());
+    	Booter.main(null);
+    }
+
+    public void set(String key, String value) {
+        System.setProperty(key, value);
+    }
+
+    public String get(String key) {
+        return System.getProperty(key);
+    }
+
+    /**
+     * 设置苍穹服务器IP地址(包括MC、ZK、文件及图片服务)
+     * 不推荐使用该方法,项目开发时应该分别配置这几个服务地址,或者优先使用MC服务器上的配置。
+     */
+    @Deprecated
+    public void setServerIp(String ip) {
+        setMcServerUrl("http://" + ip + ":8090");
+        if (!setConfigUrl) {
+            setConfigUrl(ip + ":2181");
+        }
+        setFsServerUrl(ip, 8100);
+        setImageServerUrl(ip, 8100);
+    }
+
+    /**
+     * 设置MC服务地址
+     *
+     * @param mcServerUrl
+     */
+    public void setMcServerUrl(String mcServerUrl) {
+        set("mc.server.url", mcServerUrl);
+    }
+
+    /**
+     * @param configUrl 配置服务地址
+     */
+    public void setConfigUrl(String configUrl) {
+        set(ConfigUtils.CONFIG_URL_KEY, configUrl);
+        setConfigUrl = true;
+    }
+
+    /**
+     * 配置服务地址
+     *
+     * @param connectString zookeeper链接URL,如 127.0.0.1:2181
+     * @param user          用户
+     * @param password      密码
+     */
+    public void setConfigUrl(String connectString, String user, String password) {
+        if (user != null && password != null) {
+            setConfigUrl(connectString + "?user=" + user + "&password=" + password);
+        } else {
+            setConfigUrl(connectString);
+        }
+    }
+
+    /**
+     * @param clusterNumber 集群编码
+     */
+    public void setClusterNumber(String clusterNumber) {
+        set(ConfigUtils.CLUSTER_NAME_KEY, clusterNumber);
+    }
+
+
+    /**
+     * @param appName 本节点服务名称
+     */
+    public void setAppName(String appName) {
+        setAppName(appName, true);
+    }
+
+    public void setAppName(String appName, boolean alsoSetQueueTag) {
+        set(ConfigUtils.APP_NAME_KEY, appName);
+        if (alsoSetQueueTag) {
+            setQueueTag(appName);
+        }
+    }
+
+    public void setStartWithQing(boolean b) {
+        set("bos.app.special.deployalone.ids", b ? " " : "qing");
+    }
+
+    /**
+     * @param tenantNumber 租户编码
+     */
+    public void setTenantNumber(String tenantNumber) {
+        set("domain.tenantCode", tenantNumber);
+    }
+
+    /**
+     * @param enable 是否开启水平分表服务
+     */
+    public void setXdbEnable(boolean enable) {
+        set("xdb.enable", String.valueOf(enable));
+    }
+
+    /**
+     * @param tag 队列标记
+     */
+    public void setQueueTag(String tag) {
+        set("mq.debug.queue.tag", tag);
+    }
+
+    /**
+     * @param path web静态资源路径
+     */
+    public void setWebResPath(String path) {
+        set("JETTY_WEBRES_PATH", path);
+    }
+    
+    /**
+     * 控制台输出SQL开关
+     *
+     * @param outSql        是否输出SQL
+     * @param withParameter 是否输出参数
+     */
+    public void setSqlOut(boolean outSql, boolean withParameter) {
+        set("db.sql.out", String.valueOf(outSql));
+        set("db.sql.out.withParameter", String.valueOf(withParameter));
+    }
+
+    /**
+     * 设置苍穹服务端口
+     * @param port
+     */
+    public void setCosmicWepPort(int port) {
+    	this.cosmicPort = port;
+    	this.cosmicUrl = "http://127.0.0.1:" + cosmicPort + "/ierp";
+        set("JETTY_WEB_PORT", String.valueOf(cosmicPort));
+        set("domain.contextUrl", cosmicUrl);
+    }
+    
+    /**
+     * 设置是否注册为MQ消费者
+     * @param registerOnMq
+     * @param debbugTopic
+     */
+    public void setMqConsumerRegister(boolean registerOnMq, String debbugTopic) {
+    	set("mq.consumer.register", String.valueOf(registerOnMq));
+    	if(StringUtils.isNotBlank(debbugTopic)) {
+    		set("mq.debug.queue.tag", debbugTopic);
+    	}
+    }
+    
+    /**
+     * 设置文件服务地址
+     * @param ip
+     * @param port
+     */
+    public void setFsServerUrl(String ip, int port) {
+    	set("fileserver", "http://" + ip + ":" + port + "/fileserver/");
+    	set("attachmentServer.url", "http://" + ip + ":" + port + "/fileserver/");
+    	set("attachmentServer.inner.url", "http://" + ip + ":" + port + "/fileserver/");
+    }
+    
+    /**
+     * 设置图片服务地址
+     * @param ip
+     * @param port
+     */
+    public void setImageServerUrl(String ip, int port) {
+        set("imageServer.url", "http://" + ip + ":" + port + "/fileserver/");
+        set("imageServer.inner.url", "http://" + ip + ":" + port + "/fileserver/");
+    }
+
+    /**
+     * 获取苍穹服务URL
+     */
+	public String getCosmicUrl() {
+		return cosmicUrl;
+	}
+	
+	/**
+     * 设置webapp配置所在的目录
+     */
+	public void setWebAppPath(String path) {
+		set("JETTY_WEBAPP_PATH", path);
+	}
+	
+	/**
+	 * 是否以轻量级环境启动苍穹服务
+	 */
+	public void setEnableLightWeightDeploy(boolean enable) {
+		set("lightweightdeploy", String.valueOf(enable));
+		set("lightweightdeploy.services", "");
+	}
+
+	/**
+	 * Redis配置
+	 */
+	public void setRedisConfig(String redisUrl) {
+		set("redis.serversForCache", redisUrl);
+		set("redis.serversForSession", redisUrl);
+		set("algo.storage.redis.url", redisUrl);
+		set("redismodelcache.enablelua", String.valueOf(true));
+	}
+	
+	/**
+	 * MQ配置
+	 */
+	public void setMqHostConfig(String mqHost, String mqPort, String mqUser, String mqPassword, String mqVhost) {
+		String line = System.lineSeparator();
+		StringBuffer builder = new StringBuffer();
+		builder.append("type=rabbitmq").append(line).append("host=").append(mqHost).append(line).append("port=")
+				.append(mqPort).append(line).append("user=").append(mqUser).append(line).append("password=")
+				.append(mqPassword).append(line).append("vhost=").append(mqVhost);
+		set("mq.server", builder.toString());
+	}
+	
+	/**
+	 * 是否启用监控中心日志配置(是否将日志通过kafka上传到日志中心)</br>
+	 * 注:如需要启用,请先确保elk、kafka等服务已可用
+	 */
+	public void setLogConfig(boolean useMonitorLog) {
+//		String logConfigXmlContent = null;
+		String path = null;
+		if(useMonitorLog) {
+			//日志通过kafka上传到日志中心
+			path = "logback-kafka.xml";
+//			logConfigXmlContent = FileUtil.readUtf8String("classpath:logback-kafka.xml");
+		} else {
+			//本地日志配置
+			path = "logback.xml";
+//			logConfigXmlContent = FileUtil.readUtf8String("classpath:logback.xml");
+		}
+		String logConfigXmlContent;
+		try {
+			logConfigXmlContent = IOUtils.toString(getClass().getClassLoader().getResourceAsStream(path), "utf-8");
+//			logConfigXmlContent = new String(Files.readAllBytes(Paths.get(getClass().getClassLoader().getResource(path).toURI())), "UTF-8");
+			set("log.config", logConfigXmlContent);
+			set("dubbo.application.logger", "slf4j");
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+	}
+	
+	/**
+	 * Dubbo服务配置
+	 * 
+	 * @param registerProvider   是否向此注册中心注册服务,如果设为false,将只订阅,不注册
+	 * @param registerComsumer   是否向此注册中心订阅服务,如果设为false,将只注册,不订阅
+	 * @param lookupLocal   是否使用本地服务查找
+	 */
+	public void setDubboConfig(boolean registerProvider, boolean registerComsumer, boolean lookupLocal) {
+//		set("dubbo.registry.group", group);
+		set("dubbo.registry.register", String.valueOf(registerProvider));
+		set("dubbo.registry.subscribe", String.valueOf(registerComsumer));
+		//dubbo官方资料上没有支持lookupLocal配置,可能是平台扩展的功能,也可能是无效的配置
+        set("dubbo.service.lookup.local", String.valueOf(lookupLocal));
+//        set("dubbo.registry.protocol", "zookeeper");
+//        set("dubbo.registry.address", "");
+	}
+	
+	/**
+	 * Dubbo网络配置 
+	 * @param ip 默认127.0.0.1
+	 * @param port 默认28888
+	 * @param qingPort 默认30880
+	 */
+	public void setDubboHostConfig(String ip, int port, int qingPort) {
+		port = getAvailablePort(port);
+		qingPort = getAvailablePort(qingPort);
+		set("dubbo.protocol.port", String.valueOf(port));
+        set("dubbo.consumer.url", "dubbo://" + ip + ":" + port);
+        set("dubbo.consumer.url.qing", "dubbo://" + ip + ":" + qingPort);
+	}
+	
+    public int getAvailablePort(int port) {
+        try (ServerSocket serverSocket = new ServerSocket(port)) {
+            return port;
+        } catch (IOException e) {
+            return getAvailablePort(port+1);
+        }
+    }
+	
+//	/**
+//	 * 设置苍穹服务的中间件类型
+//	 * @param serverType
+//	 */
+//    public void setCosmicServerType(CosmicServerType serverType) {
+//    	if(serverType == CosmicServerType.springboot) {
+//    		set("mservice.booter.type", serverType.name());
+//    	} else {
+//    		set("webserver.type", serverType.name());
+//    	}
+//    }
+//    
+//    enum CosmicServerType{
+//    	jetty,tomcat,aas,springboot;
+//    }
+    
+	private static String getLocalHostName() {
+		InetAddress localhost = null;
+		try {
+			localhost = InetAddress.getLocalHost();
+		} catch (UnknownHostException e) {
+			return "UnknownHost";
+		}
+		return localhost.getHostName();
+	}
+	
+}

+ 181 - 0
nckd-cosmic-debug/src/main/java/nckd/cosmic/debug/LauncherByCl.java

@@ -0,0 +1,181 @@
+package kd.cosmic.server;
+
+import kd.bos.config.client.util.ConfigUtils;
+import kd.bos.service.webserver.JettyServer;
+import kd.sdk.annotation.SdkPublic;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+/**
+ * cosmic服务启动器,含默认配置,如需更改请在Application中设置。
+ */
+@SdkPublic
+public final class LauncherByCl {
+
+    private boolean setConfigUrl = false;
+
+    public LauncherByCl() {
+        setDefault();
+    }
+
+    private void setDefault() {
+        set("configAppName", "mservice,web");
+        set("webmserviceinone", "true");
+        set("file.encoding", "utf-8");
+        set("mq.consumer.register", "true");
+        set("MONITOR_HTTP_PORT", "9998");
+        set("JMX_HTTP_PORT", "9091");
+        set("dubbo.protocol.port", "28888");
+//        set("dubbo.consumer.url", "dubbo://192.168.2.125:28888");
+//        set("dubbo.consumer.url.qing", "dubbo://192.168.2.125:308801");
+        set("dubbo.consumer.url", "dubbo://10.0.55.10:28888");
+        set("dubbo.consumer.url.qing", "dubbo://10.0.55.10:30880");
+        set("dubbo.registry.register", "false");
+        set("dubbo.service.lookup.local", "true");
+        set("appSplit", "false");
+        set("tenant.code.type", "config");
+        set("JETTY_WEB_PORT", "8080");
+//        set("domain.contextUrl", "http://localhost:8080/ierp");
+        set("domain.contextUrl", "http://127.0.0.1:8080/ierp");
+        set("lightweightdeploy", "true");
+        set("redismodelcache.enablelua", "false");
+        set("lightweightdeploy.services", "");
+
+        try {
+            String logConfig = new String(Files.readAllBytes(Paths.get(getClass().getResource("log.config.xml").toURI())), "UTF-8");
+            set("log.config", logConfig);
+        } catch (Exception e) {
+            //ignore
+        }
+
+        setClusterNumber("cosmic");
+        setTenantNumber("sample");
+        setAppName("cosmic-server");
+        setStartWithQing(true);
+        setXdbEnable(false);
+        setSqlOut(true, true);
+    }
+
+    public void start() {
+        JettyServer.main(null);
+    }
+
+    public void set(String key, String value) {
+        System.setProperty(key, value);
+    }
+
+    public String get(String key) {
+        return System.getProperty(key);
+    }
+
+    /**
+     * 设置苍穹服务器IP地址
+     */
+    public void setServerIp(String ip) {
+//        setMCServerUrl("http://" + ip + ":8090");
+        setMcServerUrl("http://" + ip + ":8090/mc");
+        if (!setConfigUrl) {
+            setConfigUrl(ip + ":2181");
+        }
+        set("fileserver", "http://" + ip + ":8100/fileserver/");
+        set("imageServer.url", "http://" + ip + ":8100/fileserver/");
+    }
+
+    /**
+     * 设置MC服务地址
+     *
+     * @param mcServerUrl
+     */
+    public void setMcServerUrl(String mcServerUrl) {
+        set("mc.server.url", mcServerUrl);
+    }
+
+    /**
+     * @param configUrl 配置服务地址
+     */
+    public void setConfigUrl(String configUrl) {
+        set(ConfigUtils.CONFIG_URL_KEY, configUrl);
+        setConfigUrl = true;
+    }
+
+    /**
+     * 配置服务地址
+     *
+     * @param connectString zookeeper链接URL,如 192.168.2.125:2181
+     * @param user          用户
+     * @param password      密码
+     */
+    public void setConfigUrl(String connectString, String user, String password) {
+        if (user != null && password != null) {
+            setConfigUrl(connectString + "?user=" + user + "&password=" + password);
+        } else {
+            setConfigUrl(connectString);
+        }
+    }
+
+    /**
+     * @param clusterNumber 集群编码
+     */
+    public void setClusterNumber(String clusterNumber) {
+        set(ConfigUtils.CLUSTER_NAME_KEY, clusterNumber);
+    }
+
+
+    /**
+     * @param appName 本节点服务名称
+     */
+    public void setAppName(String appName) {
+        setAppName(appName, true);
+    }
+
+    public void setAppName(String appName, boolean alsoSetQueueTag) {
+        set(ConfigUtils.APP_NAME_KEY, appName);
+        if (alsoSetQueueTag) {
+            setQueueTag(appName);
+        }
+    }
+
+    public void setStartWithQing(boolean b) {
+        set("bos.app.special.deployalone.ids", b ? " " : "qing");
+    }
+
+    /**
+     * @param tenantNumber 租户编码
+     */
+    public void setTenantNumber(String tenantNumber) {
+        set("domain.tenantCode", tenantNumber);
+    }
+
+    /**
+     * @param enable 是否开启水平分表服务
+     */
+    public void setXdbEnable(boolean enable) {
+        set("xdb.enable", String.valueOf(enable));
+    }
+
+    /**
+     * @param tag 队列标记
+     */
+    public void setQueueTag(String tag) {
+        set("mq.debug.queue.tag", tag);
+    }
+
+    /**
+     * @param path web路徑
+     */
+    public void setWebPath(String path) {
+        set("JETTY_WEBRES_PATH", path);
+    }
+
+    /**
+     * 控制台输出SQL开关
+     *
+     * @param outSql        是否输出SQL
+     * @param withParameter 是否输出参数
+     */
+    public void setSqlOut(boolean outSql, boolean withParameter) {
+        set("db.sql.out", String.valueOf(outSql));
+        set("db.sql.out.withParameter", String.valueOf(withParameter));
+    }
+
+}

+ 63 - 0
nckd-cosmic-debug/src/main/resources/logback-kafka.xml

@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+This is a kingdee cosmic template project that is automatically generated by the Kingdee cosmic development assistant plugin. 
+If there are any issues during the use process, you can provide feedback to the kingdee developer community website.
+Website: https://developer.kingdee.com/developer?productLineId=29
+Author: liebin.zheng
+Generate Date: 2025-11-21 10:24:13
+ -->
+<configuration>
+	
+	<property name="LOG_DIR" value="logs"/>
+	<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${PID:- } --- [%t] %-40.40logger{39} : %m%n"/>
+
+	<logger name="org.apache.catalina.startup.DigesterFactory" level="ERROR"/>
+	<logger name="org.apache.catalina.util.LifecycleBase" level="ERROR"/>
+	<logger name="org.apache.coyote.http11.Http11NioProtocol" level="WARN"/>
+	<logger name="org.apache.sshd.common.util.SecurityUtils" level="WARN"/>
+	<logger name="org.apache.tomcat.util.net.NioSelectorPool" level="WARN"/>
+	<logger name="org.eclipse.jetty.util.component.AbstractLifeCycle" level="ERROR"/>
+	<logger name="org.hibernate.validator.internal.util.Version" level="WARN"/>
+	<logger name="org.springframework.boot.actuate.endpoint.jmx" level="WARN"/>
+	
+    <logger name="kd.bos.dc.utils.AccountUtils" level="WARN"/>
+    <logger name="kd.bos.license" level="WARN"/>
+    <logger name="org.apache.zookeeper" level="WARN"/>
+    <logger name="kd.bos.portal.pluginnew" level="WARN" />
+    
+	<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+		<encoder>
+			<pattern>${LOG_PATTERN}</pattern>
+		</encoder>
+	</appender>
+	<!-- 
+	<appender name="FILE"
+		class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<encoder>
+			<pattern>${LOG_PATTERN}</pattern>
+		</encoder>
+		<file>${LOG_DIR}/cosmic_out.log</file>
+		<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+			<cleanHistoryOnStart>false</cleanHistoryOnStart>
+			<fileNamePattern>${LOG_DIR}/cosmic_out_%d{yyyyMMdd}_%i.log</fileNamePattern>
+			<maxFileSize>10MB</maxFileSize>
+			<maxHistory>7</maxHistory>
+			<totalSizeCap>0</totalSizeCap>
+		</rollingPolicy>
+	</appender>
+	 -->
+	<appender name="kafka" class="kd.bos.logging.console.slf4j.logback.KafkaAppender">
+        <topic>{{clusterName}}-log</topic>
+        <brokerList>{{log.kafka.ip_port}}</brokerList>
+        <compressionType>none</compressionType>
+        <syncSend>false</syncSend>
+        <keySerializerClass>org.apache.kafka.common.serialization.StringSerializer</keySerializerClass>
+        <valueSerializerClass>org.apache.kafka.common.serialization.StringSerializer</valueSerializerClass>
+    </appender>
+	
+	<root level="INFO">
+		<appender-ref ref="CONSOLE" />
+		<appender-ref ref="kafka" />
+	</root>
+
+</configuration>

+ 63 - 0
nckd-cosmic-debug/src/main/resources/logback.xml

@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+This is a kingdee cosmic template project that is automatically generated by the Kingdee cosmic development assistant plugin. 
+If there are any issues during the use process, you can provide feedback to the kingdee developer community website.
+Website: https://developer.kingdee.com/developer?productLineId=29
+Author: liebin.zheng
+Generate Date: 2025-11-21 10:24:13
+ -->
+<configuration>
+	
+	<property name="LOG_DIR" value="logs"/>
+	<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${PID:- } --- [%t] %-40.40logger{39} : %m%n"/>
+
+	<logger name="org.apache.catalina.startup.DigesterFactory" level="ERROR"/>
+	<logger name="org.apache.catalina.util.LifecycleBase" level="ERROR"/>
+	<logger name="org.apache.coyote.http11.Http11NioProtocol" level="WARN"/>
+	<logger name="org.apache.sshd.common.util.SecurityUtils" level="WARN"/>
+	<logger name="org.apache.tomcat.util.net.NioSelectorPool" level="WARN"/>
+	<logger name="org.eclipse.jetty.util.component.AbstractLifeCycle" level="ERROR"/>
+	<logger name="org.hibernate.validator.internal.util.Version" level="WARN"/>
+	<logger name="org.springframework.boot.actuate.endpoint.jmx" level="WARN"/>
+	
+    <logger name="kd.bos.dc.utils.AccountUtils" level="WARN"/>
+    <logger name="kd.bos.license" level="WARN"/>
+    <logger name="org.apache.zookeeper" level="WARN"/>
+    <logger name="kd.bos.portal.pluginnew" level="WARN" />
+
+	<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+		<encoder>
+			<pattern>${LOG_PATTERN}</pattern>
+		</encoder>
+	</appender>
+
+	<appender name="FILE"
+		class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<encoder>
+			<pattern>${LOG_PATTERN}</pattern>
+		</encoder>
+		<file>${LOG_DIR}/cosmic_out.log</file>
+		<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+			<cleanHistoryOnStart>false</cleanHistoryOnStart>
+			<fileNamePattern>${LOG_DIR}/cosmic_out_%d{yyyyMMdd}_%i.log</fileNamePattern>
+			<maxFileSize>10MB</maxFileSize>
+			<maxHistory>7</maxHistory>
+			<totalSizeCap>0</totalSizeCap>
+		</rollingPolicy>
+	</appender>
+	<!-- 
+	<appender name="kafka" class="kd.bos.logging.console.slf4j.logback.KafkaAppender">
+        <topic>{{clusterName}}-log</topic>
+        <brokerList>{{log.kafka.ip_port}}</brokerList>
+        <compressionType>none</compressionType>
+        <syncSend>false</syncSend>
+        <keySerializerClass>org.apache.kafka.common.serialization.StringSerializer</keySerializerClass>
+        <valueSerializerClass>org.apache.kafka.common.serialization.StringSerializer</valueSerializerClass>
+    </appender>
+	 -->
+	<root level="INFO">
+		<appender-ref ref="CONSOLE" />
+		<appender-ref ref="FILE" />
+	</root>
+
+</configuration>

+ 14 - 0
nckd-fi/build.gradle

@@ -0,0 +1,14 @@
+/*
+ * This is a kingdee cosmic template project that is automatically generated by the Kingdee cosmic development assistant plugin. 
+ * If there are any issues during the use process, you can provide feedback to the kingdee developer community website.
+ * Website: https://developer.kingdee.com/developer?productLineId=29
+ * Author: liebin.zheng
+ * Generate Date: 2025-11-21 10:24:13
+ */
+
+dependencies {
+	api project(':nckd-base-common')
+	api project(':nckd-base-helper')
+} 
+
+

+ 22 - 0
nckd-fi/src/main/java/nckd/fi/er/common/AppflgConstant.java

@@ -0,0 +1,22 @@
+/**
+ * This is a kingdee cosmic template project that is automatically generated by the Kingdee cosmic development assistant plugin. 
+ * If there are any issues during the use process, you can provide feedback to the kingdee developer community website.
+ * Website: https://developer.kingdee.com/developer?productLineId=29
+ * Author: liebin.zheng
+ * Generate Date: 2025-11-21 10:24:13
+ */
+package nckd.fi.er.common;
+
+/**
+ * fi云er应用-通用常量类<br>
+ * 代码中不能存在硬编码敏感信息,如账号、密码、http外链、ftp外链、邮箱等。<br>
+ * 标识或缓存的常量,需以"KEY_"、"FID_"、"ENTRY_"或"SUBENTRY_"作为变量的前缀。<br>
+ *
+ * @author nckd
+ * @date 2025-11-21 10:24:13
+ */
+public class AppflgConstant {
+	
+	public static final String KEY_APP_NAME = "fi-er";
+
+}

+ 104 - 0
nckd-fi/src/main/java/nckd/fi/er/common/BaseFieldConst.java

@@ -0,0 +1,104 @@
+package nckd.fi.er.common;
+
+/**
+ * 基本字段标识(苍穹默认字段标识)
+ *
+ * @date 2024/3/31
+ */
+public class BaseFieldConst {
+    /**表单主键*/
+    public static final String ID = "id";
+    /**表单主键*/
+    public static final String PID = ".id";
+    /** 多选基础资料对象 */
+    public static final String FBASEDATAID = "fbasedataid";
+    /** 多选基础资料ID */
+    public static final String FBASEDATAID_ID = "fbasedataid_id";
+    /**主数据内码 */
+    public static final String MASTER_ID = "masterid";
+    /** 可用状态 */
+    public static final String ENABLE = "enable";
+    /** 基础资料编码 */
+    public static final String NUMBER = "number";
+
+    /** 基础资料编码 */
+    public static final String PNUMBER = ".number";
+    /** 基础资料名称 */
+    public static final String NAME = "name";
+    /** 基础资料名称 */
+    public static final String PNAME = ".name";
+    /** 基础资料状态 */
+    public static final String STATUS="status";
+
+    /** 树形基础资料-长编码 */
+    public static final String LONG_NUMBER = "longnumber";
+    /** 树形基础资料-长名称 */
+    public static final String FULL_NAME = "fullname";
+    /** 树形基础资料-上级组织 */
+    public static final String PARENT = "parent";
+    /** 树形基础资料-是否叶子 */
+    public static final String IS_LEAF = "isleaf";
+    /** 树形基础资料-级次 */
+    public static final String LEVEL = "level";
+
+    /** 单据编码 */
+    public static final String BILL_NO = "billno";
+    /** 单据编码 */
+    public static final String PBILLNO = ".billno";
+    /** 单据状态 */
+    public static final String BILL_STATUS = "billstatus";
+
+    /** 币别 */
+    public static final String CURRENCY = "currency";
+    /** 金额币种精度 */
+    public static final String AMT_PRECISION = "amtprecision";
+    /** 单价币种精度 */
+    public static final String PRICE_PRECISION = "priceprecision";
+
+    /** 创建人 */
+    public static final String CREATOR = "creator";
+    /** 创建时间 */
+    public static final String CREATE_TIME = "createtime";
+    /** 所属组织 */
+    public static final String ORG = "org";
+    /** 创建组织*/
+    public static final String CREATE_ORG = "createorg";
+    /** 业务组织*/
+    public static final String USE_ORG = "useorg";
+    /** 修改人 */
+    public static final String MODIFIER = "modifier";
+    /** 修改时间 */
+    public static final String MODIFY_TIME = "modifytime";
+    /** 审核人 */
+    public static final String AUDITOR = "auditor";
+    /** 审核时间 */
+    public static final String AUDIT_DATE = "auditdate";
+    /** 禁用人 */
+    public static final String DISABLER = "disabler";
+    /** 禁用时间 */
+    public static final String DISABLE_DATE = "disabledate";
+    /** 分录序号 */
+    public static final String SEQ = "seq";
+
+    /** 附件面板 */
+    public static final String ATTACHMENTPANEL = "attachmentpanel";
+
+    /** 默认分录名 */
+    public static final String ENTRY_ENTITY = "entryentity";
+    /** 默认子单据体分录名 */
+    public static final String SUB_ENTRY_ENTITY = "subentryentity";
+    /** 默认单据列表 */
+    public static final String BILL_LIST_AP = "billlistap";
+    /** 默认工具栏 */
+    public static final String TOOL_BAR_AP = "toolbarap";
+    /** 默认高级面板工具栏 */
+    public static final String ADVCONTOOLBARAP = "advcontoolbarap";
+
+    /** 猪场 */
+    public static final String PIGFARM = "pigfarm";
+    /** 服务部 */
+    public static final String SRVFILE = "srvfile";
+    /** 单据体列表标识 */
+    public static final String BILLLISTAP = "billlistap";
+
+}

+ 61 - 0
nckd-fi/src/main/java/nckd/fi/er/common/BillStatusEnum.java

@@ -0,0 +1,61 @@
+
+package nckd.fi.er.common;
+
+public enum BillStatusEnum {
+    SAVE("暂存", "A"),
+    SUBMIT("已提交", "B"),
+    AUDIT("已审核", "C"),
+    CLOSE("已关闭", "D"),
+    ENABLE("可用", "1"),
+    UN_ENABLE("禁用", "0"),
+    TRUE("是", "1"),
+    FALSE("否", "0");
+
+    private final String name;
+    private final String value;
+
+    private BillStatusEnum(String name, String value) {
+        this.name = name;
+        this.value = value;
+    }
+
+    public String toString() {
+        return this.name + ":" + this.value;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public String getValue() {
+        return this.value;
+    }
+
+    public static BillStatusEnum getValue(String status) {
+        BillStatusEnum[] var1 = values();
+        int var2 = var1.length;
+
+        for(int var3 = 0; var3 < var2; ++var3) {
+            BillStatusEnum item = var1[var3];
+            if (item.getValue().equals(status)) {
+                return item;
+            }
+        }
+
+        return null;
+    }
+
+    public static String getValueByName(String name) {
+        BillStatusEnum[] var1 = values();
+        int var2 = var1.length;
+
+        for(int var3 = 0; var3 < var2; ++var3) {
+            BillStatusEnum item = var1[var3];
+            if (item.getName().equals(name)) {
+                return item.getValue();
+            }
+        }
+
+        return null;
+    }
+}

+ 47 - 0
nckd-fi/src/main/java/nckd/fi/er/webapi/model/BaseDataModel.java

@@ -0,0 +1,47 @@
+package nckd.fi.er.webapi.model;
+
+import java.io.Serializable;
+import kd.bos.openapi.common.custom.annotation.ApiModel;
+import kd.bos.openapi.common.custom.annotation.ApiParam;
+
+@ApiModel
+public class BaseDataModel extends BaseModel implements Serializable {
+    private static final long serialVersionUID = 1L;
+    private @ApiParam("ID") String id;
+    private @ApiParam("编码") String number;
+    private @ApiParam("名称") String name;
+    public static final String ALL_PROPERTY = "id,number,name";
+
+    public String getId() {
+        return this.id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getNumber() {
+        return this.number;
+    }
+
+    public void setNumber(String number) {
+        this.number = number;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public BaseDataModel(String id, String number, String name) {
+        this.id = id;
+        this.number = number;
+        this.name = name;
+    }
+
+    public BaseDataModel() {
+    }
+}

+ 15 - 0
nckd-fi/src/main/java/nckd/fi/er/webapi/model/BaseModel.java

@@ -0,0 +1,15 @@
+package nckd.fi.er.webapi.model;
+
+import kd.bos.openapi.common.custom.annotation.ApiModel;
+
+import java.io.Serializable;
+
+/**
+ * @Author:Zt
+ * @Date:2025/12/4
+ **/
+@ApiModel
+public class BaseModel implements Serializable {
+    public BaseModel() {
+    }
+}

+ 175 - 0
nckd-fi/src/main/java/nckd/fi/er/webapi/model/BdAttachmentModel.java

@@ -0,0 +1,175 @@
+package nckd.fi.er.webapi.model;
+
+import java.io.Serializable;
+import java.util.Date;
+import kd.bos.openapi.common.custom.annotation.ApiModel;
+import kd.bos.openapi.common.custom.annotation.ApiParam;
+
+@ApiModel
+public class BdAttachmentModel extends BaseModel implements Serializable {
+    private @ApiParam("id") String id;
+    private @ApiParam("编码") String number;
+    private @ApiParam("附件名称") String name;
+    private @ApiParam("附件状态") String status;
+    private @ApiParam("创建人") BaseDataModel creator;
+    private @ApiParam("修改人") BaseDataModel modifier;
+    private @ApiParam("使用状态") String enable;
+    private @ApiParam("创建时间") Date createTime;
+    private @ApiParam("修改时间") Date modifyTime;
+    private @ApiParam("附件id") String uid;
+    private @ApiParam("预览地址") String previewUrl;
+    private @ApiParam("附件类型") String type;
+    private @ApiParam("临时文件") String tempFile;
+    private @ApiParam("页面编码") String pageId;
+    private @ApiParam("备注") String description;
+    private @ApiParam("附件地址") String url;
+    private @ApiParam("附件大小") Long size;
+    private @ApiParam("排序字段") Integer sort;
+
+    public BdAttachmentModel() {
+    }
+
+    public String getId() {
+        return this.id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getNumber() {
+        return this.number;
+    }
+
+    public void setNumber(String number) {
+        this.number = number;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getStatus() {
+        return this.status;
+    }
+
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    public BaseDataModel getCreator() {
+        return this.creator;
+    }
+
+    public void setCreator(BaseDataModel creator) {
+        this.creator = creator;
+    }
+
+    public BaseDataModel getModifier() {
+        return this.modifier;
+    }
+
+    public void setModifier(BaseDataModel modifier) {
+        this.modifier = modifier;
+    }
+
+    public String getEnable() {
+        return this.enable;
+    }
+
+    public void setEnable(String enable) {
+        this.enable = enable;
+    }
+
+    public Date getCreateTime() {
+        return this.createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public Date getModifyTime() {
+        return this.modifyTime;
+    }
+
+    public void setModifyTime(Date modifyTime) {
+        this.modifyTime = modifyTime;
+    }
+
+    public String getUid() {
+        return this.uid;
+    }
+
+    public void setUid(String uid) {
+        this.uid = uid;
+    }
+
+    public String getPreviewUrl() {
+        return this.previewUrl;
+    }
+
+    public void setPreviewUrl(String previewUrl) {
+        this.previewUrl = previewUrl;
+    }
+
+    public String getType() {
+        return this.type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getTempFile() {
+        return this.tempFile;
+    }
+
+    public void setTempFile(String tempFile) {
+        this.tempFile = tempFile;
+    }
+
+    public String getPageId() {
+        return this.pageId;
+    }
+
+    public void setPageId(String pageId) {
+        this.pageId = pageId;
+    }
+
+    public String getDescription() {
+        return this.description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public String getUrl() {
+        return this.url;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    public Long getSize() {
+        return this.size;
+    }
+
+    public void setSize(Long size) {
+        this.size = size;
+    }
+
+    public Integer getSort() {
+        return this.sort;
+    }
+
+    public void setSort(Integer sort) {
+        this.sort = sort;
+    }
+}

+ 10 - 0
nckd-fi/src/main/java/nckd/fi/er/webapi/model/BosUserContactEtModel.java

@@ -0,0 +1,10 @@
+package nckd.fi.er.webapi.model;
+
+import java.io.Serializable;
+
+/**
+ * @Author:Zt
+ * @Date:2025/12/4
+ **/
+public class BosUserContactEtModel extends BaseModel implements Serializable {
+}

+ 25 - 0
nckd-fi/src/main/java/nckd/fi/er/webapi/model/BosUserEntityModel.java

@@ -0,0 +1,25 @@
+package nckd.fi.er.webapi.model;
+
+import kd.bos.openapi.common.custom.annotation.ApiParam;
+
+import java.io.Serializable;
+
+/**
+ * @Author:Zt
+ * @Date:2025/12/4
+ **/
+public class BosUserEntityModel extends BaseModel implements Serializable {
+
+    @ApiParam(value = "id")
+    private String id ;
+    @ApiParam(value = "部门分录.分录行号")
+    private int seq ;
+    @ApiParam(value = "部门分录.负责人")
+    private boolean isincharge ;
+    @ApiParam(value = "部门分录.兼职")
+    private Boolean ispartjob ;
+    @ApiParam(value = "部门分录.职位")
+    private String position ;
+    @ApiParam(value = "岗位")
+    private BaseDataModel post ;
+}

+ 87 - 0
nckd-fi/src/main/java/nckd/fi/er/webapi/model/BosUserModel.java

@@ -0,0 +1,87 @@
+package nckd.fi.er.webapi.model;
+
+import kd.bos.openapi.common.custom.annotation.ApiModel;
+import kd.bos.openapi.common.custom.annotation.ApiParam;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @Author:Zt
+ * @Date:2025/12/4
+ **/
+@ApiModel
+public class BosUserModel extends BaseModel implements Serializable {
+    @ApiParam(value = "id")
+    private String id ;
+    @ApiParam(value = "姓名")
+    private String name ;
+    @ApiParam(value = "工号")
+    private String number ;
+    @ApiParam(value = "数据状态")
+    private String status ;
+    @ApiParam(value = "使用状态")
+    private String enable ;
+    @ApiParam(value = "创建时间")
+    private Date createtime ;
+    @ApiParam(value = "修改时间")
+    private Date modifytime ;
+    @ApiParam(value = "主数据内码")
+    private String masterid ;
+    @ApiParam(value = "禁用时间")
+    private Date disabledate ;
+    @ApiParam(value = "性别")
+    private String gender ;
+    @ApiParam(value = "生日")
+    private Date birthday ;
+    @ApiParam(value = "证件号码")
+    private String idcard ;
+
+    @ApiParam(value = "姓名全拼")
+    private String fullpinyin ;
+    @ApiParam(value = "姓名简拼")
+    private String simplepinyin ;
+    @ApiParam(value = "用户类型")
+    private String type ;
+    @ApiParam(value = "授权状态")
+    private String authorstatus ;
+    @ApiParam(value = "用户禁用")
+    private boolean isforbidden ;
+    @ApiParam(value = "是否锁定")
+    private boolean islocked ;
+    @ApiParam(value = "开始日期")
+    private Date startdate ;
+    @ApiParam(value = "结束日期")
+    private Date enddate ;
+    @ApiParam(value = "激活状态")
+    private boolean isactived ;
+    @ApiParam(value = "注册状态")
+    private boolean isregisted ;
+    @ApiParam(value = "用户名")
+    private String username ;
+    @ApiParam(value = "数据来源")
+    private String source ;
+    @ApiParam(value = "锁定日期")
+    private String lockedtime ;
+    @ApiParam(value = "邮箱")
+    private String email ;
+    @ApiParam(value = "用户禁用时间")
+    private Date userdisabletime ;
+    @ApiParam(value = "使用系统结束日期")
+    private Date useenddate ;
+    @ApiParam(value = "排序码")
+    private String sortnumber ;
+    @ApiParam(value = "手机")
+    private String phone ;
+    @ApiParam(value = "部门分录")
+    private List<BosUserEntityModel> entityentry ;
+
+    @ApiParam(value = "联系人分录")
+    private List<BosUserContactEtModel> contactentity ;
+
+    @ApiParam(value = "类型")
+    private List<UserTypeModel> usertypes ;
+
+
+}

+ 10 - 0
nckd-fi/src/main/java/nckd/fi/er/webapi/model/UserTypeModel.java

@@ -0,0 +1,10 @@
+package nckd.fi.er.webapi.model;
+
+import java.io.Serializable;
+
+/**
+ * @Author:Zt
+ * @Date:2025/12/4
+ **/
+public class UserTypeModel extends BaseModel implements Serializable {
+}

+ 135 - 0
nckd-fi/src/main/java/nckd/fi/er/webapi/utils/BdAttachmentUtils.java

@@ -0,0 +1,135 @@
+//
+// Source code recreated from a .class file by IntelliJ IDEA
+// (powered by FernFlower decompiler)
+//
+
+package nckd.fi.er.webapi.utils;
+
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import kd.bos.context.RequestContext;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.id.ID;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.attachment.AttachmentFieldServiceHelper;
+import kd.bos.servicehelper.operation.SaveServiceHelper;
+import nckd.fi.er.common.BillStatusEnum;
+import nckd.fi.er.webapi.model.BaseDataModel;
+import nckd.fi.er.webapi.model.BdAttachmentModel;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+
+public class BdAttachmentUtils {
+    public BdAttachmentUtils() {
+    }
+
+    public static DynamicObject[] getAttachmentDynamicObject(List<String> uidList) {
+        QFilter filter = new QFilter("uid", "in", uidList);
+        return BusinessDataServiceHelper.load("bd_attachment", "id,number,name,status,creator,modifier,enable,createtime,modifytime,masterid,uid,previewurl,type,tempfile,pageid,description,url,size,sort", filter.toArray());
+    }
+
+    public static List<BdAttachmentModel> getAttachments(List<String> uidList) {
+        DynamicObject[] loads = getAttachmentDynamicObject(uidList);
+        return ArrayUtils.isNotEmpty(loads) ? DoMoConvertUtils.toListModel(loads, new BdAttachmentModel()) : null;
+    }
+
+    public static Object[] saveAttachments(List<BdAttachmentModel> attachmentModelList) {
+        if (CollectionUtils.isEmpty(attachmentModelList)) {
+            return null;
+        } else {
+            DynamicObject[] oldAttachmentDynamicObject = getAttachmentDynamicObject((List)attachmentModelList.stream().map(BdAttachmentModel::getUid).collect(Collectors.toList()));
+            Map<String, DynamicObject> oldAttachmentMap = new HashMap(0);
+            if (ArrayUtils.isNotEmpty(oldAttachmentDynamicObject)) {
+                oldAttachmentMap = (Map)Arrays.stream(oldAttachmentDynamicObject).collect(Collectors.toMap((d) -> {
+                    return d.getString("uid");
+                }, (d) -> {
+                    return d;
+                }));
+            }
+
+            String uidPre = "rc-upload-" + System.currentTimeMillis() + "-";
+            int uidNum = (new SecureRandom()).nextInt(100);
+            Date today = new Date();
+            BaseDataModel userBaseDataModel = new BaseDataModel();
+            userBaseDataModel.setId(String.valueOf(RequestContext.get().getCurrUserId()));
+            List<DynamicObject> changeDynamicObjectList = new ArrayList();
+            List<DynamicObject> addDynamicObjectList = new ArrayList();
+            ArrayList<String> addUids = new ArrayList();
+            Iterator var10 = attachmentModelList.iterator();
+
+            while(var10.hasNext()) {
+                BdAttachmentModel attachmentModel = (BdAttachmentModel)var10.next();
+                String id = attachmentModel.getId();
+                if (StringUtils.isEmpty(id)) {
+                    id = String.valueOf(ID.genLongId());
+                    attachmentModel.setId(id);
+                }
+
+                String number = attachmentModel.getNumber();
+                if (StringUtils.isEmpty(number)) {
+                    number = UUID.randomUUID().toString();
+                    attachmentModel.setNumber(number);
+                }
+
+                String name = attachmentModel.getName();
+                attachmentModel.setCreator(userBaseDataModel);
+                attachmentModel.setModifier(userBaseDataModel);
+                attachmentModel.setModifyTime(today);
+                attachmentModel.setCreateTime(today);
+                String uid = attachmentModel.getUid();
+                if (StringUtils.isEmpty(uid)) {
+                    uid = uidPre + uidNum++;
+                    attachmentModel.setUid(uid);
+                }
+
+                attachmentModel.setType(name != null ? name.substring(name.lastIndexOf(46) + 1) : "");
+                attachmentModel.setTempFile("1");
+                String url = attachmentModel.getUrl();
+                if (url.contains("tempfile/download.do?configKey") && StringUtils.isNotEmpty(name)) {
+                    String path = AttachmentFieldServiceHelper.saveTempToFileService(url, id, name);
+                    attachmentModel.setUrl(path);
+                }
+
+                DynamicObject attachmentDynamicObject = (DynamicObject)((Map)oldAttachmentMap).remove(uid);
+                if (null == attachmentDynamicObject) {
+                    attachmentDynamicObject = BusinessDataServiceHelper.newDynamicObject("bd_attachment");
+                    DoMoConvertUtils.toDynamicObject(attachmentDynamicObject, attachmentModel);
+                    attachmentDynamicObject.set("status", BillStatusEnum.SUBMIT.getValue());
+                    addDynamicObjectList.add(attachmentDynamicObject);
+                    addUids.add(attachmentModel.getUid());
+                } else {
+                    attachmentModel.setId(attachmentDynamicObject.getString("id"));
+                    BaseDataModel oldUserBaseDataModel = new BaseDataModel();
+                    oldUserBaseDataModel.setId(attachmentDynamicObject.getString("creator.id"));
+                    attachmentModel.setCreator(oldUserBaseDataModel);
+                    attachmentModel.setCreateTime(attachmentDynamicObject.getDate("createtime"));
+                    DoMoConvertUtils.toDynamicObject(attachmentDynamicObject, attachmentModel);
+                    attachmentDynamicObject.set("status", BillStatusEnum.SUBMIT.getValue());
+                    changeDynamicObjectList.add(attachmentDynamicObject);
+                }
+            }
+
+            if (!CollectionUtils.isEmpty(addDynamicObjectList)) {
+                SaveServiceHelper.save((DynamicObject[])addDynamicObjectList.toArray(new DynamicObject[0]));
+                DynamicObject[] newAttachs = getAttachmentDynamicObject(addUids);
+                changeDynamicObjectList.addAll(Arrays.asList(newAttachs));
+            }
+
+            if (changeDynamicObjectList.size() > 0) {
+                return SaveServiceHelper.save((DynamicObject[])changeDynamicObjectList.toArray(new DynamicObject[0]));
+            } else {
+                return null;
+            }
+        }
+    }
+}

+ 502 - 0
nckd-fi/src/main/java/nckd/fi/er/webapi/utils/DoMoConvertUtils.java

@@ -0,0 +1,502 @@
+package nckd.fi.er.webapi.utils;
+
+import cn.hutool.core.util.StrUtil;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.dataentity.entity.MulBasedataDynamicObjectCollection;
+import kd.bos.dataentity.metadata.dynamicobject.DynamicObjectType;
+import kd.bos.dataentity.metadata.dynamicobject.DynamicProperty;
+import kd.bos.dataentity.utils.ArrayUtils;
+import kd.bos.entity.property.BasedataProp;
+import kd.bos.entity.property.RefBillProp;
+import kd.bos.exception.KDBizException;
+import kd.bos.logging.Log;
+import kd.bos.logging.LogFactory;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import nckd.fi.er.common.BaseFieldConst;
+import nckd.fi.er.webapi.model.BaseModel;
+import nckd.fi.er.webapi.model.BdAttachmentModel;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.BooleanUtils;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.beans.IntrospectionException;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.*;
+import java.math.BigDecimal;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @author 许永财-金蝶
+ * @module 销售管理平台
+ * @description DynamicObject model 转换工具类
+ * @since 2025/3/5
+ */
+public class DoMoConvertUtils {
+	private static final Log logger = LogFactory.getLog(DoMoConvertUtils.class);
+
+	private DoMoConvertUtils() {
+	}
+
+	public static <T> T toModel(DynamicObject dynamicObject, T t) {
+		return toModel(dynamicObject, t, new HashMap<>(16));
+	}
+
+	/**
+	 * DynamicObject 转成 model
+	 * 1、model需要继承 xn.sbreed.common.model.base.BaseModel
+	 * 2、约定model的变量名转成小写后,可以从dynamicObject.get 获取对应的值,如果不满足,可以用keyMap转换
+	 * <p>
+	 * HashMap<String, String> keyMap = new HashMap<>();
+	 * keyMap.put("PigArchivesListModel_id", "pigarchives.id");
+	 * keyMap.put("PigArchivesListModel_name", "pigarchives.name");
+	 * DoMoConvertUtils.toListModel(doArr, new PigArchivesListModel(), keyMap);
+	 *
+	 * @param dynamicObject
+	 * @param t
+	 * @param keyMap
+	 * @param <T>
+	 * @return
+	 */
+	public static <T> T toModel(DynamicObject dynamicObject, T t, Map<String, String> keyMap) {
+		if (null == dynamicObject || null == t) {
+			return null;
+		}
+		Class<?> aClass = t.getClass();
+		Field[] declaredFields = aClass.getDeclaredFields();
+		String aClassSimpleName = aClass.getSimpleName();
+		Field[] allFields = getFields(aClass);
+		try {
+			for (Field field : allFields) {
+				String name = field.getName();
+
+				String convertKey = keyMap.get(aClassSimpleName + StrUtil.C_UNDERLINE + name);
+
+				if (StringUtils.isBlank(convertKey)) {
+					convertKey = name.toLowerCase();
+				}
+				if (!isContainsProperty(dynamicObject, convertKey)) {
+					continue;
+				}
+				if (null == dynamicObject.get(convertKey)) {
+					continue;
+				}
+				PropertyDescriptor propertyDescriptor = new PropertyDescriptor(name, aClass);
+				Method writeMethod = propertyDescriptor.getWriteMethod();
+
+				if (field.getType() == String.class) {
+					Object obj = dynamicObject.get(convertKey);
+					writeMethod.invoke(t, String.valueOf(obj));
+				} else if (field.getType() == Long.class) {
+					writeMethod.invoke(t, dynamicObject.getLong(convertKey));
+				} else if (field.getType() == Date.class) {
+					writeMethod.invoke(t, dynamicObject.getDate(convertKey));
+				} else if (field.getType() == Boolean.class) {
+					writeMethod.invoke(t, dynamicObject.getBoolean(convertKey));
+				} else if (field.getType() == Double.class) {
+					writeMethod.invoke(t, dynamicObject.getBigDecimal(convertKey).doubleValue());
+				} else if (field.getType() == Integer.class) {
+					writeMethod.invoke(t, Integer.valueOf(dynamicObject.getInt(convertKey)));
+				} else if (field.getType() == BigDecimal.class) {
+					writeMethod.invoke(t, dynamicObject.getBigDecimal(convertKey));
+				} else if (BaseModel.class.isAssignableFrom(field.getType())) {
+					Class<?> type = field.getType();
+					Object newObj = type.newInstance();
+					DynamicObject bBo = dynamicObject.getDynamicObject(convertKey);
+					Object obj = toModel(bBo, newObj, keyMap);
+					writeMethod.invoke(t, obj);
+				} else if (field.getType() == List.class) {
+					Type genericType = field.getGenericType();
+					if (null == genericType) {
+						continue;
+					}
+					if (genericType instanceof ParameterizedType) {
+						ParameterizedType pt = (ParameterizedType) genericType;
+						// 得到泛型里的class类型对象
+						Class<?> actualTypeArgument = (Class<?>) pt.getActualTypeArguments()[0];
+						Object actualType = actualTypeArgument.newInstance();
+						DynamicObjectCollection dc = dynamicObject.getDynamicObjectCollection(convertKey);
+						if (CollectionUtils.isEmpty(dc)) {
+							continue;
+						}
+
+						if (dc instanceof MulBasedataDynamicObjectCollection) {
+							//多选基础资料
+							List<Object> objects = toModelHandelMulBaseDc(dc, actualType, keyMap);
+							writeMethod.invoke(t, objects);
+						} else {
+							List<Object> objects = toListModel(dc, actualType, keyMap);
+							writeMethod.invoke(t, objects);
+						}
+
+
+					}
+				} else if (field.getType() == Set.class) {
+					Type genericType = field.getGenericType();
+					if (null == genericType) {
+						continue;
+					}
+					if (genericType instanceof ParameterizedType) {
+						ParameterizedType pt = (ParameterizedType) genericType;
+						// 得到泛型里的class类型对象
+						Class<?> actualTypeArgument = (Class<?>) pt.getActualTypeArguments()[0];
+						Object actualType = actualTypeArgument.newInstance();
+						DynamicObjectCollection dc = dynamicObject.getDynamicObjectCollection(convertKey);
+						if (CollectionUtils.isEmpty(dc)) {
+							continue;
+						}
+						if (dc instanceof MulBasedataDynamicObjectCollection) {
+							//多选基础资料
+							List<Object> list = toModelHandelMulBaseDc(dc, actualType, keyMap);
+							Set<Object> set = new HashSet<>();
+							set.addAll(list);
+							writeMethod.invoke(t, set);
+						} else {
+							Set<Object> objects = toSetModel(dc, actualType, keyMap);
+							writeMethod.invoke(t, objects);
+						}
+
+					}
+				}
+			}
+			return t;
+		} catch (IntrospectionException e) {
+			throw new KDBizException("IntrospectionException" + e.getLocalizedMessage());
+		} catch (InvocationTargetException e) {
+			throw new KDBizException("InvocationTargetException" + e.getLocalizedMessage());
+		} catch (IllegalAccessException e) {
+			throw new KDBizException("IllegalAccessException" + e.getLocalizedMessage());
+		} catch (InstantiationException e) {
+			throw new KDBizException("InstantiationException" + e.getLocalizedMessage());
+		}
+	}
+
+	/**
+	 * 根据baseModel的变量,把对应的值设置到dynamicObject
+	 *
+	 * @param dynamicObject
+	 * @param baseModel
+	 * @param isSetNull     是否把null的值设置到DynamicObject,更新操作时有用
+	 * @param keyMap
+	 * @return
+	 */
+	public static DynamicObject toDynamicObject(DynamicObject dynamicObject, BaseModel baseModel, Boolean isSetNull, Map<String, String> keyMap) {
+		if (null == dynamicObject) {
+			return null;
+		}
+		if (null == baseModel) {
+			return dynamicObject;
+		}
+		Class<? extends BaseModel> aClass = baseModel.getClass();
+		String aClassSimpleName = aClass.getSimpleName();
+		Field[] allFields = getFields(aClass);
+		try {
+			for (Field field : allFields) {
+				String name = field.getName();
+				String convertKey = keyMap.get(aClassSimpleName + StrUtil.C_UNDERLINE + name);
+
+				if (StringUtils.isBlank(convertKey)) {
+					convertKey = name.toLowerCase();
+				}
+				if (!isContainsProperty(dynamicObject, convertKey)) {
+					continue;
+				}
+				PropertyDescriptor propertyDescriptor = new PropertyDescriptor(name, aClass);
+				Method readMethod = propertyDescriptor.getReadMethod();
+				Object valueObj = readMethod.invoke(baseModel);
+				if (ObjectUtils.isEmpty(valueObj)) {
+					if (BooleanUtils.isTrue(isSetNull)) {
+						if (field.getType() == List.class || (field.getType() == Set.class)) {
+							DynamicObjectCollection dynamicObjectCollection = dynamicObject.getDynamicObjectCollection(convertKey);
+							if (!CollectionUtils.isEmpty(dynamicObjectCollection)) {
+								dynamicObjectCollection.clear();
+							}
+						} else {
+							dynamicObject.set(convertKey, null);
+						}
+					}
+					continue;
+				}
+				// 单据状态不转换
+				if (StringUtils.equalsIgnoreCase(BaseFieldConst.STATUS, name) || StringUtils.equalsIgnoreCase(BaseFieldConst.BILL_STATUS, name)) {
+					continue;
+				}
+				if (field.getType() == String.class) {
+					if (BaseFieldConst.ID.equals(name)) {
+						dynamicObject.set(BaseFieldConst.ID, Long.valueOf(valueObj.toString()));
+					} else {
+						dynamicObject.set(convertKey, valueObj.toString());
+					}
+				} else if (field.getType() == Long.class) {
+					dynamicObject.set(convertKey, Long.valueOf(valueObj.toString()));
+				} else if (field.getType() == Date.class) {
+					dynamicObject.set(convertKey, valueObj);
+				} else if (field.getType() == Boolean.class) {
+					dynamicObject.set(convertKey, valueObj);
+				} else if (field.getType() == Double.class) {
+					dynamicObject.set(convertKey, valueObj);
+				} else if (field.getType() == Integer.class) {
+					dynamicObject.set(convertKey, valueObj);
+				} else if (field.getType() == BigDecimal.class) {
+					dynamicObject.set(convertKey, valueObj);
+				} else if (BaseModel.class.isAssignableFrom(field.getType())) {
+					Class<?> baseModelClass = valueObj.getClass();
+					PropertyDescriptor basePropertyDescriptor = new PropertyDescriptor("id", baseModelClass);
+					Method baseReadMethod = basePropertyDescriptor.getReadMethod();
+					Object idValue = baseReadMethod.invoke(valueObj);
+					if (ObjectUtils.isEmpty(idValue)) {
+						if (BooleanUtils.isTrue(isSetNull)) {
+							dynamicObject.set(convertKey, null);
+						}
+					} else {
+						DynamicProperty property = dynamicObject.getDynamicObjectType().getProperty(convertKey);
+						if (property instanceof BasedataProp) {
+							BasedataProp basedataProp = (BasedataProp) property;
+							DynamicObject baseDataObj = BusinessDataServiceHelper.newDynamicObject(basedataProp.getBaseEntityId());
+							toDynamicObject(baseDataObj, (BaseModel) valueObj, isSetNull, keyMap);
+							dynamicObject.set(convertKey, baseDataObj);
+						} else if (property instanceof RefBillProp) {
+							RefBillProp refBillProp = (RefBillProp) property;
+							DynamicObject refBillObj = BusinessDataServiceHelper.newDynamicObject(refBillProp.getBillEntityId());
+							toDynamicObject(refBillObj, (BaseModel) valueObj, isSetNull, keyMap);
+							dynamicObject.set(convertKey, refBillObj);
+						} else {
+							dynamicObject.set(convertKey, Long.valueOf(idValue.toString()));
+						}
+
+					}
+				} else if (Collection.class.isAssignableFrom(field.getType())) {
+					Type genericType = field.getGenericType();
+					if (null == genericType) {
+						continue;
+					}
+					if (genericType instanceof ParameterizedType) {
+						ParameterizedType pt = (ParameterizedType) genericType;
+						// 得到泛型里的class类型对象
+						Class<?> actualTypeArgument = (Class<?>) pt.getActualTypeArguments()[0];
+
+						//特殊处理附件字段
+						if (BdAttachmentModel.class.isAssignableFrom(actualTypeArgument)) {
+							//附件字段
+							Object[] objects = BdAttachmentUtils.saveAttachments((List<BdAttachmentModel>) valueObj);
+							if (ArrayUtils.isNotEmpty(objects)) {
+								for (Object object : objects) {
+									DynamicObject subDynamicObject = dynamicObject.getDynamicObjectCollection(name).addNew();
+									subDynamicObject.set(BaseFieldConst.FBASEDATAID, object);
+								}
+							}
+						} else if (BaseModel.class.isAssignableFrom(actualTypeArgument)) {
+							//只处理baseModel
+							DynamicObjectCollection dc = dynamicObject.getDynamicObjectCollection(convertKey);
+							toDynamicObjHandelDc(dc, (Collection<BaseModel>) valueObj, keyMap);
+						}
+					}
+
+				}
+
+			}
+		} catch (IntrospectionException e) {
+			throw new KDBizException("IntrospectionException" + e.getLocalizedMessage());
+		} catch (InvocationTargetException e) {
+			throw new KDBizException("InvocationTargetException" + e.getLocalizedMessage());
+		} catch (IllegalAccessException e) {
+			throw new KDBizException("IllegalAccessException" + e.getLocalizedMessage());
+		}
+
+		return dynamicObject;
+	}
+
+	public static DynamicObject toDynamicObject(DynamicObject dynamicObject, BaseModel baseModel, Map<String, String> keyMap) {
+		return toDynamicObject(dynamicObject, baseModel, false, new HashMap<>(16));
+
+	}
+
+	public static DynamicObject toDynamicObject(DynamicObject dynamicObject, BaseModel baseModel) {
+		return toDynamicObject(dynamicObject, baseModel, new HashMap<>(16));
+	}
+
+	public static DynamicObject toDynamicObject(DynamicObject dynamicObject, BaseModel baseModel, Boolean isSetNull) {
+		return toDynamicObject(dynamicObject, baseModel, isSetNull, new HashMap<>(16));
+	}
+
+	public static <T> List<T> toListModel(DynamicObject[] dc, T t) {
+		return toListModel(dc, t, new HashMap<>(16));
+	}
+
+	public static <T> List<T> toListModel(DynamicObjectCollection dc, T t) {
+		return toListModel(dc, t, new HashMap<>(16));
+	}
+
+	public static <T> List<T> toListModel(DynamicObject[] dc, T t, Map<String, String> keyMap) {
+		List<T> list = new ArrayList<>();
+		toCollModel(dc, t, list, keyMap);
+		return list;
+	}
+
+	public static <T> List<T> toListModel(DynamicObjectCollection dc, T t, Map<String, String> keyMap) {
+		List<T> list = new ArrayList<>();
+		toCollModel(dc, t, list, keyMap);
+		return list;
+	}
+
+	public static <T> Set<T> toSetModel(DynamicObject[] dc, T t) {
+		return toSetModel(dc, t, new HashMap<>(16));
+	}
+
+	public static <T> Set<T> toSetModel(DynamicObjectCollection dc, T t) {
+		return toSetModel(dc, t, new HashMap<>(16));
+	}
+
+	public static <T> Set<T> toSetModel(DynamicObject[] dc, T t, Map<String, String> keyMap) {
+		HashSet<T> set = new HashSet<>();
+		toCollModel(dc, t, set, keyMap);
+		return set;
+	}
+
+	public static <T> Set<T> toSetModel(DynamicObjectCollection dc, T t, Map<String, String> keyMap) {
+		HashSet<T> set = new HashSet<>();
+		toCollModel(dc, t, set, keyMap);
+		return set;
+	}
+
+	private static <T> Collection<T> toCollModel(DynamicObject[] dc, T t, Collection<T> collection, Map<String, String> keyMap) {
+		for (DynamicObject dynamicObject : dc) {
+			try {
+				T newInstance = (T) t.getClass().newInstance();
+				T newModel = toModel(dynamicObject, newInstance, keyMap);
+				collection.add(newModel);
+			} catch (InstantiationException e) {
+				throw new KDBizException("InstantiationException" + e.getLocalizedMessage());
+			} catch (IllegalAccessException e) {
+				throw new KDBizException("IllegalAccessException" + e.getLocalizedMessage());
+			}
+		}
+		return collection;
+	}
+
+	private static <T> Collection<T> toCollModel(DynamicObjectCollection dc, T t, Collection<T> collection, Map<String, String> keyMap) {
+		DynamicObject[] objects = dc.toArray(new DynamicObject[dc.size()]);
+		return toCollModel(objects, t, collection, keyMap);
+	}
+
+	private static boolean isContainsProperty(DynamicObject dynamicObject, String property) {
+		if (StringUtils.isBlank(property)) {
+			return false;
+		}
+		if (null == dynamicObject) {
+			return false;
+		}
+		if (property.contains(StrUtil.DOT)) {
+			String[] perArr = property.split("\\.");
+			if (!dynamicObject.containsProperty(perArr[0])) {
+				return false;
+			}
+			String per = StringUtils.substring(property, property.indexOf(StrUtil.DOT) + 1);
+			if (dynamicObject.get(perArr[0]) instanceof DynamicObject) {
+				return isContainsProperty(dynamicObject.getDynamicObject(perArr[0]), per);
+			} else {
+				return false;
+			}
+		} else {
+			return dynamicObject.containsProperty(property);
+		}
+	}
+
+	/**
+	 * 多选基础资料处理
+	 *
+	 * @param dc
+	 * @param t
+	 * @param keyMap
+	 * @param <T>
+	 * @return
+	 */
+	private static <T> List<T> toModelHandelMulBaseDc(DynamicObjectCollection dc, T t, Map<String, String> keyMap) {
+		List<T> list = new ArrayList<>();
+		for (DynamicObject row : dc) {
+			try {
+				T newInstance = (T) t.getClass().newInstance();
+				Object o = row.get(BaseFieldConst.FBASEDATAID);
+				if (o instanceof DynamicObject) {
+					DynamicObject dynamicObject = row.getDynamicObject(BaseFieldConst.FBASEDATAID);
+					toModel(dynamicObject, newInstance, keyMap);
+					list.add(newInstance);
+				}
+			} catch (InstantiationException e) {
+				throw new KDBizException("InstantiationException" + e.getLocalizedMessage());
+			} catch (IllegalAccessException e) {
+				throw new KDBizException("IllegalAccessException" + e.getLocalizedMessage());
+			} catch (Exception e) {
+				logger.info("[DoMoConvertUtils工具类]toModelHandelMulBaseDc异常:row {}", row);
+				throw e;
+			}
+		}
+		return list;
+	}
+
+
+	private static void toDynamicObjHandelDc(DynamicObjectCollection dc, Collection<BaseModel> collection, Map<String, String> keyMap) throws IntrospectionException, InvocationTargetException, IllegalAccessException {
+		if (dc instanceof MulBasedataDynamicObjectCollection) {
+			//多选基础资料
+			dc.clear();
+			DynamicObjectType dynamicObjectType = dc.getDynamicObjectType();
+			for (BaseModel obj : collection) {
+				Class<? extends BaseModel> aClass = obj.getClass();
+				PropertyDescriptor basePropertyDescriptor = new PropertyDescriptor(BaseFieldConst.ID, aClass);
+				Method baseReadMethod = basePropertyDescriptor.getReadMethod();
+				Object baseValue = baseReadMethod.invoke(obj);
+
+				DynamicObject newDo = new DynamicObject(dynamicObjectType);
+				newDo.set("fbasedataid", baseValue);
+				dc.add(newDo);
+			}
+
+		} else {
+			if (CollectionUtils.isEmpty(dc)) {
+				for (BaseModel obj : collection) {
+					DynamicObject dynamicObject = dc.addNew();
+					toDynamicObject(dynamicObject, obj, keyMap);
+				}
+			} else {
+				//修改,方便前端操作,后端先删除再插入,这里要注意,目前都假定分录id的变化不会影响上下游数据
+				dc.clear();
+				for (BaseModel obj : collection) {
+					DynamicObject dynamicObject = dc.addNew();
+					toDynamicObject(dynamicObject, obj, keyMap);
+				}
+			}
+
+		}
+	}
+
+	/**
+	 * 获取到所有的字段,包含父类
+	 *
+	 * @param aClass
+	 * @return
+	 */
+	private static Field[] getFields(Class<?> aClass) {
+		List<Field> fieldList = new ArrayList<>(16);
+		Set<String> fieldNameSet = new HashSet<>(16);
+
+		Class<?> curClass = aClass;
+		while (curClass != null) {
+			Field[] declaredFields = curClass.getDeclaredFields();
+			if (fieldNameSet.isEmpty()) {
+				List<String> collect = Arrays.stream(declaredFields).map(Field::getName).collect(Collectors.toList());
+				fieldNameSet.addAll(collect);
+				fieldList.addAll(Arrays.asList(declaredFields));
+			} else {
+				List<Field> collect = Arrays.stream(declaredFields).filter(field -> !fieldNameSet.contains(field.getName())).collect(Collectors.toList());
+				fieldList.addAll(collect);
+				Set<String> collectName = collect.stream().map(Field::getName).collect(Collectors.toSet());
+				fieldNameSet.addAll(collectName);
+			}
+			curClass = curClass.getSuperclass();
+		}
+
+		return fieldList.toArray(new Field[fieldList.size()]);
+	}
+}

+ 14 - 0
nckd-hr/build.gradle

@@ -0,0 +1,14 @@
+/*
+ * This is a kingdee cosmic template project that is automatically generated by the Kingdee cosmic development assistant plugin. 
+ * If there are any issues during the use process, you can provide feedback to the kingdee developer community website.
+ * Website: https://developer.kingdee.com/developer?productLineId=29
+ * Author: liebin.zheng
+ * Generate Date: 2025-11-24 15:27:17
+ */
+
+dependencies {
+	api project(':nckd-base-common')
+	api project(':nckd-base-helper')
+} 
+
+

+ 67 - 0
nckd-hr/src/main/java/nckd/hr/hrpi/plugin/operate/SyncUserServiceEx.java

@@ -0,0 +1,67 @@
+package nckd.hr.hrpi.plugin.operate;
+
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.dataentity.utils.ObjectUtils;
+import kd.bos.exception.KDBizException;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.permission.model.UserParam;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.hr.hbp.business.service.ext.ISyncUserService;
+import kd.hr.hbp.common.ext.SyncParamModel;
+import nckd.hr.hspm.common.constant.BaseFieldConst;
+import nckd.hr.hspm.common.util.QFilterUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+public class SyncUserServiceEx implements ISyncUserService {
+    /**
+     * 替换生成平台用户的用户名为人员工号
+     * SyncParamModel的属性 UserParam,是同步到平台的入参
+     * UserParam.getUserParam()是参数的具体入参,修改dataMap 的值put替换标品的就可以 SyncParamModel 里面有自然人信息可以用来查询
+     * @param syncParamModel
+     */
+    @Override
+    public void updateSyncUserParam(SyncParamModel syncParamModel) {
+        UserParam userParam = syncParamModel.getUserParam();
+        Map<String, Object> dataMap = userParam.getDataMap();
+        if(!ObjectUtils.isEmpty(dataMap)) {
+            String number = syncParamModel.getPersonDy().getString(BaseFieldConst.NUMBER);   //人员工号
+
+            //查询带有标识的人员
+            QFilter qFilter = QFilterUtils.getBaseDataQFilter();
+            qFilter.and(new QFilter(BaseFieldConst.NUMBER, QCP.equals,number));
+            DynamicObject bosUsers = BusinessDataServiceHelper.loadSingle("bos_user",qFilter.toArray());
+
+            if(bosUsers != null) {
+                DynamicObjectCollection collection = bosUsers.getDynamicObjectCollection(BaseFieldConst.ENTRY_ENTITY);
+                //遍历entryentity
+                ArrayList<DynamicObject> list = new ArrayList<>();
+                for (DynamicObject dynamicObject : collection) {
+                    //判断dynamicObject内的字段position是否有值,有值则用list集合添加
+                    if (dynamicObject.getDynamicObject("post") == null) {
+                        list.add(dynamicObject);
+                    }
+                }
+                if (!list.isEmpty()) {
+                    //如果符合条件,添加分录
+                    ArrayList<HashMap<String, Object>> entryEntity = (ArrayList<HashMap<String, Object>>) dataMap.get("entryentity");
+                    HashMap<String, Object> addMap = new HashMap<>();
+                    for (DynamicObject entry : list) {
+                        addMap.put("position", entry.getString("position"));
+                        addMap.put("ispartjob", "1");
+                        addMap.put("dpt", entry.getLong("dpt.id"));
+                        addMap.put("superior", entry.getLong("superior.id"));
+                        addMap.put("isincharge", entry.getBoolean("isincharge"));
+                        entryEntity.add(addMap);
+                    }
+                    dataMap.put("entryentity", entryEntity);
+                }
+            }
+        }
+    }
+}

+ 22 - 0
nckd-hr/src/main/java/nckd/hr/hspm/common/AppflgConstant.java

@@ -0,0 +1,22 @@
+/**
+ * This is a kingdee cosmic template project that is automatically generated by the Kingdee cosmic development assistant plugin. 
+ * If there are any issues during the use process, you can provide feedback to the kingdee developer community website.
+ * Website: https://developer.kingdee.com/developer?productLineId=29
+ * Author: liebin.zheng
+ * Generate Date: 2025-11-24 15:27:17
+ */
+package nckd.hr.hspm.common;
+
+/**
+ * hr云hspm应用-通用常量类<br>
+ * 代码中不能存在硬编码敏感信息,如账号、密码、http外链、ftp外链、邮箱等。<br>
+ * 标识或缓存的常量,需以"KEY_"、"FID_"、"ENTRY_"或"SUBENTRY_"作为变量的前缀。<br>
+ *
+ * @author nckd
+ * @date 2025-11-24 15:27:17
+ */
+public class AppflgConstant {
+	
+	public static final String KEY_APP_NAME = "hr-hspm";
+
+}

+ 104 - 0
nckd-hr/src/main/java/nckd/hr/hspm/common/constant/BaseFieldConst.java

@@ -0,0 +1,104 @@
+package nckd.hr.hspm.common.constant;
+
+/**
+ * 基本字段标识(苍穹默认字段标识)
+ *
+ * @date 2024/3/31
+ */
+public class BaseFieldConst {
+    /**表单主键*/
+    public static final String ID = "id";
+    /**表单主键*/
+    public static final String PID = ".id";
+    /** 多选基础资料对象 */
+    public static final String FBASEDATAID = "fbasedataid";
+    /** 多选基础资料ID */
+    public static final String FBASEDATAID_ID = "fbasedataid_id";
+    /**主数据内码 */
+    public static final String MASTER_ID = "masterid";
+    /** 可用状态 */
+    public static final String ENABLE = "enable";
+    /** 基础资料编码 */
+    public static final String NUMBER = "number";
+
+    /** 基础资料编码 */
+    public static final String PNUMBER = ".number";
+    /** 基础资料名称 */
+    public static final String NAME = "name";
+    /** 基础资料名称 */
+    public static final String PNAME = ".name";
+    /** 基础资料状态 */
+    public static final String STATUS="status";
+
+    /** 树形基础资料-长编码 */
+    public static final String LONG_NUMBER = "longnumber";
+    /** 树形基础资料-长名称 */
+    public static final String FULL_NAME = "fullname";
+    /** 树形基础资料-上级组织 */
+    public static final String PARENT = "parent";
+    /** 树形基础资料-是否叶子 */
+    public static final String IS_LEAF = "isleaf";
+    /** 树形基础资料-级次 */
+    public static final String LEVEL = "level";
+
+    /** 单据编码 */
+    public static final String BILL_NO = "billno";
+    /** 单据编码 */
+    public static final String PBILLNO = ".billno";
+    /** 单据状态 */
+    public static final String BILL_STATUS = "billstatus";
+
+    /** 币别 */
+    public static final String CURRENCY = "currency";
+    /** 金额币种精度 */
+    public static final String AMT_PRECISION = "amtprecision";
+    /** 单价币种精度 */
+    public static final String PRICE_PRECISION = "priceprecision";
+
+    /** 创建人 */
+    public static final String CREATOR = "creator";
+    /** 创建时间 */
+    public static final String CREATE_TIME = "createtime";
+    /** 所属组织 */
+    public static final String ORG = "org";
+    /** 创建组织*/
+    public static final String CREATE_ORG = "createorg";
+    /** 业务组织*/
+    public static final String USE_ORG = "useorg";
+    /** 修改人 */
+    public static final String MODIFIER = "modifier";
+    /** 修改时间 */
+    public static final String MODIFY_TIME = "modifytime";
+    /** 审核人 */
+    public static final String AUDITOR = "auditor";
+    /** 审核时间 */
+    public static final String AUDIT_DATE = "auditdate";
+    /** 禁用人 */
+    public static final String DISABLER = "disabler";
+    /** 禁用时间 */
+    public static final String DISABLE_DATE = "disabledate";
+    /** 分录序号 */
+    public static final String SEQ = "seq";
+
+    /** 附件面板 */
+    public static final String ATTACHMENTPANEL = "attachmentpanel";
+
+    /** 默认分录名 */
+    public static final String ENTRY_ENTITY = "entryentity";
+    /** 默认子单据体分录名 */
+    public static final String SUB_ENTRY_ENTITY = "subentryentity";
+    /** 默认单据列表 */
+    public static final String BILL_LIST_AP = "billlistap";
+    /** 默认工具栏 */
+    public static final String TOOL_BAR_AP = "toolbarap";
+    /** 默认高级面板工具栏 */
+    public static final String ADVCONTOOLBARAP = "advcontoolbarap";
+
+    /** 猪场 */
+    public static final String PIGFARM = "pigfarm";
+    /** 服务部 */
+    public static final String SRVFILE = "srvfile";
+    /** 单据体列表标识 */
+    public static final String BILLLISTAP = "billlistap";
+
+}

+ 84 - 0
nckd-hr/src/main/java/nckd/hr/hspm/common/enums/BillStatusEnums.java

@@ -0,0 +1,84 @@
+package nckd.hr.hspm.common.enums;
+
+/**
+ * 标准状态枚举类
+ **/
+@SuppressWarnings("all")
+public enum BillStatusEnums {
+
+    /**
+     * 暂存
+     */
+    SAVE("暂存", "A"),
+
+    /**
+     * 已提交
+     */
+    SUBMIT("已提交", "B"),
+
+    /**
+     * 已审核
+     */
+    AUDIT("已审核", "C"),
+
+    /**
+     * 启用
+     */
+    ENABLE("启用", "1"),
+
+    /**
+     * 禁用
+     */
+    UN_ENABLE("禁用", "0"),
+
+    /**
+     * 作废
+     */
+    CANCEL("作废", "F"),
+
+    /**
+     * true
+     */
+    TRUE("是", "1"),
+
+    /**
+     * false
+     */
+    FALSE("否", "0");
+
+    /**字段定义**/
+    private final String name;
+    /**字段定义**/
+    private final String value;
+
+    BillStatusEnums(String name, String value) {
+        this.name = name;
+        this.value = value;
+    }
+
+    @Override
+    public String toString() {
+        return name + ":" + value;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    /**
+     * 根据单据状态获取枚举对象
+     */
+    public static BillStatusEnums getByValue(String status) {
+        for (BillStatusEnums billStatusEnum : values()) {
+            if (billStatusEnum.getValue().equals(status)) {
+                return billStatusEnum;
+            }
+        }
+        return null;
+    }
+
+}

+ 734 - 0
nckd-hr/src/main/java/nckd/hr/hspm/common/util/DateUtil.java

@@ -0,0 +1,734 @@
+package nckd.hr.hspm.common.util;
+
+
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.utils.ObjectUtils;
+import kd.bos.exception.ErrorCode;
+import kd.bos.exception.KDBizException;
+import kd.bos.exception.KDException;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.util.StringUtils;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.*;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalAdjusters;
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * 日期工具类
+ *
+ * @author xyc
+ * @date 2021/8/4
+ */
+public final class DateUtil {
+
+	public static final String DATE_FORMAT_YYYY_MM_DD = "yyyy-MM-dd";
+
+	public static final String DATE_TIME_FORMAT_YYYY_MM_DD_HH_MI_SS = "yyyy-MM-dd HH:mm:ss";
+
+	public static final String DATE_TIME_FORMAT_YYYY_MM_DD_HH_MI_SS_SSS = "yyyyMMddHHmmssSSS";
+
+	public static final String DATE_FORMAT_YYYYMM = "yyyyMM";
+
+	public static final String DATE_FORMAT_YYYYMMDD = "yyyyMMdd";
+
+	public static final ZoneId UTC_PLUS_8 = ZoneId.systemDefault();
+
+	private DateUtil() {
+	}
+
+	public static boolean isCurrentDateInRange(Date startDate, Date endDate) {
+		Date currentDate = new Date();
+		return isDateInRange(currentDate, startDate, endDate);
+	}
+
+	public static boolean isDateInRange(Date targetDate, Date startDate, Date endDate) {
+		return !targetDate.before(startDate) && !targetDate.after(endDate);
+	}
+
+	/**
+	 * 日期转字符串
+	 *
+	 * @param date    指定日期
+	 * @param pattern 格式
+	 * @return 返回String格式 ( yyyy-MM-dd HH:mm:ss )
+	 */
+	public static String date2str(Date date, String pattern) {
+		if (null == date) {
+			return null;
+		}
+		SimpleDateFormat format = null;
+		if (StringUtils.isNotEmpty(pattern)) {
+			format = new SimpleDateFormat(pattern);
+		} else {
+			format = new SimpleDateFormat(DATE_TIME_FORMAT_YYYY_MM_DD_HH_MI_SS);
+		}
+		return format.format(date);
+	}
+
+	/**
+	 * 获取当前时间字符串
+	 *
+	 * @param pattern 格式
+	 * @return String
+	 */
+	public static String getCurrentDateTimeStr(String pattern) {
+		return date2str(new Date(), pattern);
+	}
+
+	/**
+	 * 字符串转日期
+	 *
+	 * @param str     指定日期
+	 * @param pattern 格式
+	 * @return date
+	 */
+	public static Date string2date(String str, String pattern) {
+		SimpleDateFormat format = null;
+		if (StringUtils.isNotEmpty(pattern)) {
+			format = new SimpleDateFormat(pattern);
+		} else {
+			format = new SimpleDateFormat(DATE_TIME_FORMAT_YYYY_MM_DD_HH_MI_SS);
+		}
+		Date date = null;
+		if (!StringUtils.isEmpty(str)) {
+			try {
+				date = format.parse(str);
+			} catch (ParseException e) {
+				throw new KDException(new ErrorCode("time convert error", "日期%s转换异常"), str);
+			}
+		}
+		return date;
+	}
+
+
+	/**
+	 * 将指定日期的时分秒格式那天的 00:00:00 的时间
+	 *
+	 * @param date 指定日期
+	 * @return date
+	 */
+	public static Date getDateStartTime(Date date) {
+		if (null == date) {
+			return null;
+		}
+		LocalDate localDate = date.toInstant().atZone(UTC_PLUS_8).toLocalDate();
+		LocalDateTime localDateTime = localDate.atStartOfDay();
+		return localDateTime2date(localDateTime);
+	}
+
+	/**
+	 * 将指定日期的时分秒格式那天的 23:59:59 的时间
+	 *
+	 * @param date 指定日期
+	 * @return date
+	 */
+	public static Date getDateEndTime(Date date) {
+		LocalDate localDate = date.toInstant().atZone(UTC_PLUS_8).toLocalDate();
+		LocalDateTime localDateTime = localDate.atTime(23, 59, 59);
+		return localDateTime2date(localDateTime);
+	}
+
+	/**
+	 * 获取指定日期当月最后一天的时间
+	 *
+	 * @param date 指定日期
+	 * @return 最后一天的时间 (时分秒:23:59:59)
+	 */
+	public static Date getTimeEndOfMonth(Date date) {
+		LocalDate localDate = date.toInstant().atZone(UTC_PLUS_8).toLocalDate();
+		LocalDateTime localDateTime = localDate.atTime(23, 59, 59).with(TemporalAdjusters.lastDayOfMonth());
+		return localDateTime2date(localDateTime);
+	}
+
+	/**
+	 * 月结束日期
+	 *
+	 * @param date 指定日期
+	 * @return date
+	 */
+	public static Date getDateEndOfMonth(Date date) {
+		LocalDate localDate = date.toInstant().atZone(UTC_PLUS_8).toLocalDate();
+		localDate = localDate.withDayOfMonth(localDate.lengthOfMonth());
+		return Date.from(localDate.atStartOfDay().atZone(UTC_PLUS_8).toInstant());
+	}
+
+	/**
+	 * 获取指定日期当月第一天的时间
+	 *
+	 * @param date 指定日期
+	 * @return 第一天的时间 (时分秒:00:00:00)
+	 */
+	public static Date getDateStartOfMonth(Date date) {
+		LocalDate localDate = date.toInstant().atZone(UTC_PLUS_8).toLocalDate();
+		LocalDateTime localDateTime = localDate.atStartOfDay().with(TemporalAdjusters.firstDayOfMonth());
+		return localDateTime2date(localDateTime);
+	}
+
+	/**
+	 * 周开始日期
+	 *
+	 * @param date        指定日期
+	 * @param isChinaWeek 是否按中国的习惯(一个星期的第一天是星期一)
+	 * @return Date
+	 */
+	public static Date getDateStartOfWeek(Date date, boolean isChinaWeek) {
+		LocalDate localDate = date.toInstant().atZone(UTC_PLUS_8).toLocalDate();
+		int days = localDate.getDayOfWeek().getValue() % 7;
+		if (isChinaWeek) {
+			days = days - 1;
+		}
+		localDate = localDate.minusDays(days);
+		return Date.from(localDate.atStartOfDay().atZone(UTC_PLUS_8).toInstant());
+	}
+
+	/**
+	 * 周结束日期
+	 *
+	 * @param date        指定日期
+	 * @param isChinaWeek 是否按中国的习惯(一个星期的第一天是星期一)
+	 * @return Date
+	 */
+	public static Date getDateEndOfWeek(Date date, boolean isChinaWeek) {
+		LocalDate localDate = date.toInstant().atZone(UTC_PLUS_8).toLocalDate();
+		int days = localDate.getDayOfWeek().getValue() % 7;
+		if (isChinaWeek) {
+			days = days - 1;
+		}
+		localDate = localDate.plusDays(7 - 1 - days);
+		return Date.from(localDate.atStartOfDay().atZone(UTC_PLUS_8).toInstant());
+	}
+
+	/**
+	 * 获取指定日期当年第一天的时间
+	 *
+	 * @param date 指定日期
+	 * @return date
+	 */
+	public static Date getDateStartOfYear(Date date) {
+		LocalDate localDate = date.toInstant().atZone(UTC_PLUS_8).toLocalDate();
+		localDate = localDate.withMonth(1).withDayOfMonth(1);
+		return Date.from(localDate.atStartOfDay().atZone(UTC_PLUS_8).toInstant());
+	}
+
+	/**
+	 * 获取指定日期当年最后一天的时间
+	 *
+	 * @param date 指定日期
+	 * @return date
+	 */
+	public static Date getDateEndOfYear(Date date) {
+		LocalDate localDate = date.toInstant().atZone(UTC_PLUS_8).toLocalDate();
+		localDate = localDate.withMonth(12);
+		localDate = localDate.withDayOfMonth(localDate.lengthOfMonth());
+		return Date.from(localDate.atStartOfDay().atZone(UTC_PLUS_8).toInstant());
+	}
+
+	/**
+	 * 获取年龄
+	 *
+	 * @param birthDate
+	 * @param currentDate
+	 * @return
+	 */
+	public static int getAge(Date birthDate, Date currentDate) {
+		if (ObjectUtils.isEmpty(birthDate)) {
+			throw new KDBizException("生日不能为空");
+		}
+		Date date = currentDate;
+		if (ObjectUtils.isEmpty(date)) {
+			date = new Date();
+		}
+		LocalDate birthLocalDate = date2localDate(birthDate);
+		LocalDate currentLocalDate = date2localDate(date);
+		return (int) birthLocalDate.until(currentLocalDate, ChronoUnit.DAYS);
+	}
+
+	/**
+	 * 获取俩个日期间天数,如果入参为空时返回0
+	 *
+	 * @param firstDate 开始时间
+	 * @param secDate   结束时间
+	 * @return 返回俩个日期间天数的绝对值
+	 */
+	public static int daysBetweenIfBlankReturnZero(Date firstDate, Date secDate) {
+		if (firstDate == null || secDate == null) {
+			return 0;
+		}
+		return daysBetween(firstDate, secDate);
+	}
+
+	/**
+	 * 获取俩个日期间天数 ( 正整数 )
+	 *
+	 * @param firstDate 开始时间
+	 * @param secDate   结束时间
+	 * @return 返回俩个日期间天数的绝对值
+	 */
+	public static int daysBetween(Date firstDate, Date secDate) {
+		LocalDate localDate1 = date2localDate(firstDate);
+		LocalDate localDate2 = date2localDate(secDate);
+		return localDate2.isAfter(localDate1) ? (int) localDate1.until(localDate2, ChronoUnit.DAYS) : (int) localDate2.until(localDate1, ChronoUnit.DAYS);
+	}
+
+	/**
+	 * 获得指定时间加减月数后的日期
+	 *
+	 * @param date  指定日期
+	 * @param month 月数,可正可负
+	 * @return 计算后的日期
+	 */
+	public static Date addMonth(Date date, int month) {
+		LocalDateTime localDateTime = date2localDateTime(date);
+		return localDateTime2date(localDateTime.plusMonths(month));
+	}
+
+	/**
+	 * 获得指定时间加减星期后的日期
+	 *
+	 * @param date 指定日期
+	 * @param week 星期,可正可负
+	 * @return 计算后的日期
+	 */
+	public static Date addWeek(Date date, int week) {
+		LocalDateTime localDateTime = date2localDateTime(date);
+		return localDateTime2date(localDateTime.plusWeeks(week));
+	}
+
+	/**
+	 * 获得指定时间第二天的日期
+	 *
+	 * @param date 指定时间
+	 * @return 计算后的日期
+	 */
+	public static Date getNextDay(Date date) {
+		LocalDateTime localDateTime = date2localDateTime(date);
+		return localDateTime2date(localDateTime.plusDays(1L));
+	}
+
+	/**
+	 * 获得指定时间加减天数后的日期
+	 *
+	 * @param date 指定日期
+	 * @param days 天数,可正可负
+	 * @return 计算后的日期
+	 */
+	public static Date addDay(Date date, int days) {
+		LocalDateTime localDateTime = date2localDateTime(date);
+		return localDateTime2date(localDateTime.plusDays(days));
+	}
+
+	/**
+	 * 获得指定时间加减小时后的时间
+	 *
+	 * @param date  指定日期
+	 * @param hours 小时数,可正可负
+	 * @return 计算后的日期
+	 */
+	public static Date addHour(Date date, int hours) {
+		LocalDateTime localDateTime = date2localDateTime(date);
+		return localDateTime2date(localDateTime.plusHours(hours));
+	}
+
+	/**
+	 * 获得指定时间加减小时后的时间
+	 * @param date  指定日期
+	 * @param seconds 秒,可正可负
+	 * @return 计算后的日期
+	 */
+	public static Date addSecond(Date date, int seconds) {
+		LocalDateTime localDateTime = date2localDateTime(date);
+		return localDateTime2date(localDateTime.plusSeconds(seconds));
+	}
+
+	public static String localDateTime2str(LocalDateTime localDateTime, String pattern) {
+		return localDateTime.format(DateTimeFormatter.ofPattern(pattern));
+	}
+
+	public static Date localDateTime2date(LocalDateTime localDateTime) {
+		return Date.from(localDateTime.atZone(UTC_PLUS_8).toInstant());
+	}
+
+	public static LocalDateTime date2localDateTime(Date date) {
+		return date.toInstant().atZone(UTC_PLUS_8).toLocalDateTime();
+	}
+
+	public static LocalDate date2localDate(Date date) {
+		return date.toInstant().atZone(UTC_PLUS_8).toLocalDate();
+	}
+
+	/**
+	 * 获取某个时间月份的天数
+	 *
+	 * @param date 时间
+	 * @return 返回月份的总天数
+	 */
+	public static int getDaysOfMonth(Date date) {
+		Calendar calendar = Calendar.getInstance();
+		calendar.setTime(date);
+		return calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
+	}
+
+	/**
+	 * 获取某年某月的天数
+	 *
+	 * @param year  年份
+	 * @param month 月份
+	 * @return 返回月份的总天数
+	 */
+	public static int getDaysOfMonth(int year, int month) {
+		Calendar calendar = Calendar.getInstance();
+		calendar.set(Calendar.MONTH, month - 1);
+		calendar.set(Calendar.YEAR, year);
+		return calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
+	}
+
+	public static boolean isSameDay(Date date1, Date date2) {
+		LocalDate localDate1 = date2localDate(date1);
+		LocalDate localDate2 = date2localDate(date2);
+		return localDate1.equals(localDate2);
+	}
+
+	/**
+	 * <p> 描述 : 调整日期时间</p>
+	 * <p> 备注 : hours 整数为几个小时后,负数几个小时前</p>
+	 *
+	 * @param date
+	 * @param hours
+	 * @return java.util.Date
+	 */
+	public static Date adjustDateTime(Date date, int hours) {
+		LocalDateTime localDateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
+		if (hours > 0) {
+			localDateTime = localDateTime.plusHours(hours);
+		} else {
+			localDateTime = localDateTime.minusHours(Math.abs(hours));
+		}
+		return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
+	}
+
+
+	/**
+	 * @return
+	 * @Title 想要获取的日期与传入日期的差值   比如想要获取传入日期前四天的日期 			day=-4即可
+	 * @Description
+	 * @author hang
+	 */
+	public static Date getSomeDay(Date date, int day) {
+		Calendar calendar = Calendar.getInstance();
+		calendar.setTime(date);
+		calendar.add(Calendar.DATE, day);
+		return getStartOfDay(calendar.getTime());
+	}
+
+
+	/**
+	 * @return
+	 * @Title 想要获取几个月前后的日期与传入日期的差值   比如想要获取传入日期前四天的日期
+	 * @Description
+	 * @author hang
+	 */
+	public static Date getMonthDiffDay(Date date, int day) {
+		Calendar calendar = Calendar.getInstance();
+		calendar.setTime(date);
+		calendar.add(Calendar.MONTH, day);
+		return getStartOfDay(calendar.getTime());
+	}
+
+	/**
+	 * @return
+	 * @Title 想要获取几个月前后的日期与传入日期的差值   比如想要获取传入日期前四天的日期
+	 * @Description
+	 * @author hang
+	 */
+	public static Date getYearDiffDay(Date date, int day) {
+		Calendar calendar = Calendar.getInstance();
+		calendar.setTime(date);
+		calendar.add(Calendar.YEAR, day);
+		return getStartOfDay(calendar.getTime());
+	}
+
+	/**
+	 * @param date
+	 * @return
+	 * @description: 获得当天最小时间
+	 * @author: jiang
+	 * @date: 2021年06月21日
+	 */
+	public static Date getStartOfDay(Date date) {
+		if( date != null ){
+			LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()),
+					ZoneId.systemDefault());
+			LocalDateTime startOfDay = localDateTime.with(LocalTime.MIN);
+			return Date.from(startOfDay.atZone(ZoneId.systemDefault()).toInstant());
+		}
+		return  null;
+	}
+
+	/**
+	 * @param date
+	 * @return
+	 * @description: 获得当天最大时间
+	 * @author: Jeff
+	 * @date: 2021年06月21日
+	 */
+	public static Date getEndOfDay(Date date) {
+		LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()),
+				ZoneId.systemDefault());
+		LocalDateTime endOfDay = localDateTime.with(LocalTime.MAX);
+		return Date.from(endOfDay.atZone(ZoneId.systemDefault()).toInstant());
+	}
+
+	/**
+	 * @return
+	 * @Title 计算两个日期的天数
+	 * @Description
+	 * @author hang
+	 */
+	public static int getDayDiffer(Date startDate, Date endDate) {
+		try {
+			SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+			long startDateTime = dateFormat.parse(dateFormat.format(startDate)).getTime();
+			long endDateTime = dateFormat.parse(dateFormat.format(endDate)).getTime();
+			return (int) ((endDateTime - startDateTime) / (1000 * 3600 * 24));
+		} catch (ParseException e) {
+			return 0;
+		}
+	}
+
+	public static Date getMonthFirstDay(Date thisDay, int diff) throws ParseException {
+		//时间字符串转 LocalDate 类型
+		LocalDate today = thisDay.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+		//当前月份+(-diff)
+		today = today.minusMonths(diff);
+		today = today.with(TemporalAdjusters.firstDayOfMonth());
+		SimpleDateFormat simple = new SimpleDateFormat("yyyy-MM-dd 00:00:00");
+		ZonedDateTime zonedDateTime = today.atStartOfDay(ZoneId.systemDefault());
+		String newday = simple.format(Date.from(zonedDateTime.toInstant()));
+		Date date = simple.parse(newday);
+		return date;
+	}
+
+	/**
+	 * 获取上个月指定第几天的日期(包括跨年)
+	 *
+	 * @param isEndDay   是否是最后一天  true默认上个月最后一天的日期,dayOfMonth随便传什么,false指定上个月某一天的日期
+	 * @param dayOfMonth 指定要获取的上个月的日期
+	 * @return 获取上个月最后一天的日期(包括跨年)
+	 */
+	public static Date getLastDayOfLastMonth(boolean isEndDay, int dayOfMonth) {
+		// 获取当前日期
+		LocalDate currentDate = LocalDate.now();
+		// 获取上个月的年份和月份
+		int lastMonthYear;
+		int lastMonth;
+		if (currentDate.getMonthValue() == 1) {
+			// 如果当前月份是1月,则上个月的年份为去年,月份为12
+			lastMonthYear = currentDate.getYear() - 1;
+			lastMonth = 12;
+		} else {
+			// 否则上个月的年份和月份分别为当前年份和当前月份减1
+			lastMonthYear = currentDate.getYear();
+			lastMonth = currentDate.getMonthValue() - 1;
+		}
+		// 构造上个月的YearMonth对象
+		YearMonth lastYearMonth = YearMonth.of(lastMonthYear, lastMonth);
+		LocalDate lastDayOfLastMonth;
+		if (isEndDay) {
+			// 获取上个月的最后一天
+			lastDayOfLastMonth = lastYearMonth.atEndOfMonth();
+		} else {
+			lastDayOfLastMonth = lastYearMonth.atDay(dayOfMonth);
+		}
+		return java.sql.Date.valueOf(lastDayOfLastMonth);
+	}
+
+	/**
+	 * 获取指定日期上个月指定第几天的日期(包括跨年)
+	 *
+	 * @param isEndDay   是否是最后一天  true默认上个月最后一天的日期,dayOfMonth随便传什么,false指定上个月某一天的日期
+	 * @param dayOfMonth 获取指定日期的上个月的日期
+	 * @param date       指定日期
+	 * @return 获取上个月最后一天的日期(包括跨年)
+	 */
+	public static Date getLastDayOfLastMonth(boolean isEndDay, int dayOfMonth, Date date) {
+		LocalDate currentDate = date.toInstant().atZone(UTC_PLUS_8).toLocalDate();
+		// 获取上个月的年份和月份
+		int lastMonthYear;
+		int lastMonth;
+		if (currentDate.getMonthValue() == 1) {
+			// 如果当前月份是1月,则上个月的年份为去年,月份为12
+			lastMonthYear = currentDate.getYear() - 1;
+			lastMonth = 12;
+		} else {
+			// 否则上个月的年份和月份分别为当前年份和当前月份减1
+			lastMonthYear = currentDate.getYear();
+			lastMonth = currentDate.getMonthValue() - 1;
+		}
+		// 构造上个月的YearMonth对象
+		YearMonth lastYearMonth = YearMonth.of(lastMonthYear, lastMonth);
+		LocalDate lastDayOfLastMonth;
+		if (isEndDay) {
+			// 获取上个月的最后一天
+			lastDayOfLastMonth = lastYearMonth.atEndOfMonth();
+		} else {
+			lastDayOfLastMonth = lastYearMonth.atDay(dayOfMonth);
+		}
+		return java.sql.Date.valueOf(lastDayOfLastMonth);
+	}
+
+
+	/**
+	 * 获取当月某天
+	 *
+	 * @param day
+	 * @return
+	 */
+	public static Date getDayOfMonth(int day) {
+		// 获取当前日期
+		LocalDate currentDate = LocalDate.now();
+		// 设置日期为当月的第15天
+		LocalDate fifteenthDayOfMonth = LocalDate.of(currentDate.getYear(), currentDate.getMonth(), day);
+		return java.sql.Date.valueOf(fifteenthDayOfMonth);
+	}
+
+	/**
+	 * 上个月月份
+	 *
+	 * @return 上个月月份
+	 */
+	public static int getLastMonth() {
+		// 获取当前日期
+		LocalDate currentDate = LocalDate.now();
+		// 获取上个月的日期
+		LocalDate lastMonthDate = currentDate.minusMonths(1);
+		// 获取上个月的月份
+		int lastMonth = lastMonthDate.getMonthValue();
+		return lastMonth;
+	}
+
+	/**
+	 * 判断当天是否是多少号
+	 *
+	 * @param mark 号
+	 * @return 判断当天是否是多少号
+	 */
+	public static Boolean isMark(int mark) {
+		Calendar calendar = Calendar.getInstance();
+		// 获取日期的日
+		int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
+		// 判断当天是否是多少号
+		return dayOfMonth == mark;
+	}
+
+	/**
+	 * 指定日期月份第一天
+	 *
+	 * @param date 指定日期
+	 * @return 指定日期月份第一天
+	 */
+	public static Date getFirstDayOfMonth(Date date) {
+		Calendar calendar = Calendar.getInstance();
+		calendar.setTime(date);
+		calendar.set(Calendar.DAY_OF_MONTH, 1);
+		calendar.set(Calendar.HOUR_OF_DAY, 0);
+		calendar.set(Calendar.MINUTE, 0);
+		calendar.set(Calendar.SECOND, 0);
+		calendar.set(Calendar.MILLISECOND, 0);
+		return calendar.getTime();
+	}
+
+	/**
+	 * 指定日期月份最后一天
+	 *
+	 * @param date 指定日期
+	 * @return 指定日期月份最后一天
+	 */
+	public static Date getLastDayOfMonth(Date date) {
+		Calendar calendar = Calendar.getInstance();
+		calendar.setTime(date);
+		calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
+		calendar.set(Calendar.HOUR_OF_DAY, 23);
+		calendar.set(Calendar.MINUTE, 59);
+		calendar.set(Calendar.SECOND, 59);
+		calendar.set(Calendar.MILLISECOND, 999);
+		return calendar.getTime();
+	}
+
+	/**
+	 * @param yearMonth 年月 2024-01
+	 * @return 获取月份天数
+	 */
+	public static int getDaysInMonth(String yearMonth) {
+		YearMonth ym = YearMonth.parse(yearMonth);
+		Month month = ym.getMonth();
+		if (month == Month.FEBRUARY && ym.isLeapYear()) {
+			return 29;
+		} else {
+			return month.maxLength();
+		}
+	}
+
+	public static DynamicObject getPeriodByDate(Date date) {
+		QFilter filter = new QFilter("begindate", "<=", date).and("enddate", ">=", date);
+		return BusinessDataServiceHelper.loadSingle("bd_period", filter.toArray());
+	}
+
+	public static String convertTimestamp(long timestamp) {
+		// 转换为秒
+		long totalSeconds = timestamp;
+		// 计算小时
+		long hours = totalSeconds / 3600;
+		// 计算分钟
+		long minutes = (totalSeconds % 3600) / 60;
+		// 计算秒
+		long seconds = totalSeconds % 60;
+
+		return String.format("%02d:%02d:%02d", hours, minutes, seconds);
+	}
+
+	/**
+	 * 取最大值
+	 * @param d1 d1
+	 * @param d2 d2
+	 * @return 最大值
+	 */
+	public static Date max(Date d1, Date d2) {
+		if (d1 == null) {
+			return d2;
+		}
+		if (d2 == null) {
+			return d1;
+		}
+		if (d1.after(d2)) {
+			return d1;
+		}else {
+			return d2;
+		}
+	}
+
+	/**
+	 * 取最小值
+	 * @param d1 d1
+	 * @param d2 d2
+	 * @return 最小值
+	 */
+	public static Date min(Date d1, Date d2) {
+		if (d1 == null) {
+			return d2;
+		}
+		if (d2 == null) {
+			return d1;
+		}
+		if (d1.before(d2)) {
+			return d1;
+		}else {
+			return d2;
+		}
+	}
+}

+ 161 - 0
nckd-hr/src/main/java/nckd/hr/hspm/common/util/QFilterUtils.java

@@ -0,0 +1,161 @@
+package nckd.hr.hspm.common.util;
+
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.entity.datamodel.IDataModel;
+import kd.bos.form.field.events.BeforeF7SelectEvent;
+import kd.bos.list.ListShowParameter;
+import kd.bos.org.model.EnableEnum;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import nckd.hr.hspm.common.enums.BillStatusEnums;
+import nckd.hr.hspm.common.constant.BaseFieldConst;
+
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * @Module : 繁殖云--XX应用--XX模块--XX单据
+ * @Description :
+ * @Date : 2021/3/24
+ * @Author : jiawei_mao
+ **/
+public class QFilterUtils {
+    private QFilterUtils() {
+    }
+
+    /**
+     * 拼接字段
+     * @param concat 拼接符
+     * @param prop 连接字段
+     * @return
+     */
+    public static String concatProperty(String concat, String... prop) {
+        return String.join(concat, prop);
+    }
+
+    public static QFilter getBaseDataQFilter() {
+        return new QFilter(BaseFieldConst.ENABLE, QCP.equals, EnableEnum.Enable)
+                .and(BaseFieldConst.STATUS, QCP.equals, BillStatusEnums.AUDIT.getValue());
+    }
+
+    public static QFilter getBillQFilter() {
+        return new QFilter(BaseFieldConst.BILL_STATUS, QCP.equals, BillStatusEnums.AUDIT.getValue());
+    }
+
+    public static List<QFilter> getDeDuplicationFilter(IDataModel model, String entryKey, String name, BeforeF7SelectEvent beforeF7SelectEvent) {
+        DynamicObjectCollection entryEntity = model.getEntryEntity(entryKey);
+        return getDeDuplicationFilter(entryEntity, name, beforeF7SelectEvent);
+    }
+
+    /**
+     * 基础资料去重过滤方法
+     * @param entryEntity 分录实体
+     * @param name 标识
+     * @param beforeF7SelectEvent f7筛选
+     */
+    public static List<QFilter> getDeDuplicationFilter(DynamicObjectCollection entryEntity, String name, BeforeF7SelectEvent beforeF7SelectEvent) {
+        if (Objects.isNull(entryEntity) || entryEntity.isEmpty()) {
+            return new ArrayList<>();
+        }
+        List<QFilter> qFilters = new ArrayList<>();
+        for (DynamicObject entry : entryEntity) {
+            DynamicObject dynamicObject = entry.getDynamicObject(name);
+            if (Objects.isNull(dynamicObject)) {
+                continue;
+            }
+            QFilter qFilter = new QFilter(BaseFieldConst.ID, QCP.not_equals, dynamicObject.getPkValue());
+            qFilters.add(qFilter);
+        }
+
+        ListShowParameter showParameter = (ListShowParameter) beforeF7SelectEvent.getFormShowParameter();
+        List<QFilter> qFiltersAll = showParameter.getListFilterParameter().getQFilters();
+        qFiltersAll.addAll(qFilters);
+        return qFiltersAll;
+    }
+
+    /**
+     * 且
+     * @param arr arr
+     * @return QFilter
+     */
+    public static QFilter and(QFilter... arr) {
+        QFilter filter = null;
+        for (QFilter item : arr) {
+            if (item == null) {
+                continue;
+            }
+            if (filter == null) {
+                filter = item;
+            }else {
+                filter = filter.and(item);
+            }
+        }
+        return filter;
+    }
+
+    /**
+     * 或
+     * @param arr arr
+     * @return QFilter
+     */
+    public static QFilter or(QFilter... arr) {
+        QFilter filter = null;
+        for (QFilter item : arr) {
+            if (item == null) {
+                continue;
+            }
+            if (filter == null) {
+                filter = item;
+            }else {
+                filter = filter.or(item);
+            }
+        }
+        return filter;
+    }
+
+    public static QFilter convertFilter(QFilter filter, String prop, Function<String, QFilter> function) {
+        if (filter == null) {
+            return filter;
+        }
+        String property = filter.getProperty();
+        String cp = filter.getCP();
+        Object value = filter.getValue();
+        List<QFilter.QFilterNest> nests = filter.getNests(false);
+        QFilter resultFilter = null;
+        if (property.equals(prop)) {
+            if (QCP.in.equalsIgnoreCase(cp)) {
+                Collection<String> statusList = (Collection<String>) value;
+                QFilter inFilter = null;
+                for (String status : statusList) {
+                    QFilter statusFilter = function.apply(status);
+                    inFilter = QFilterUtils.or(inFilter, statusFilter);
+                }
+                resultFilter = QFilterUtils.or(resultFilter, inFilter);
+            }else if (QCP.equals.equalsIgnoreCase(cp)){
+                String eStatusNumber = (String) value;
+                QFilter statusFilter = function.apply(eStatusNumber);
+                resultFilter = QFilterUtils.or(resultFilter, statusFilter);
+            }
+            assert resultFilter != null;
+        }else {
+            resultFilter = new QFilter(property, cp, value);
+        }
+        for (QFilter.QFilterNest nest : nests) {
+            QFilter filter2 = nest.getFilter();
+            filter2 = convertFilter(filter2, prop, function);
+            if (nest.isAnd()) {
+                resultFilter.and(filter2);
+            }else {
+                resultFilter.or(filter2);
+            }
+        }
+        return resultFilter;
+    }
+
+
+}

+ 12 - 0
nckd-hr/src/main/java/nckd/hr/hspm/plugin/operate/SyncPersonToUserExtPlugin.java

@@ -0,0 +1,12 @@
+package nckd.hr.hspm.plugin.operate;
+
+import kd.hrmp.hrpi.business.domian.service.impl.SyncPersonToUserServiceImpl;
+
+/**
+ * @Author:Zt
+ * @Date:2025/11/24
+ **/
+public class SyncPersonToUserExtPlugin extends SyncPersonToUserServiceImpl {
+
+
+}

+ 65 - 0
nckd-hr/src/main/java/nckd/hr/hspm/plugin/operate/SyncUserServiceEx.java

@@ -0,0 +1,65 @@
+package nckd.hr.hspm.plugin.operate;
+
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.dataentity.utils.ObjectUtils;
+import kd.bos.exception.KDBizException;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.permission.model.UserParam;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.hr.hbp.business.service.ext.ISyncUserService;
+import kd.hr.hbp.common.ext.SyncParamModel;
+import nckd.hr.hspm.common.constant.BaseFieldConst;
+import nckd.hr.hspm.common.util.QFilterUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+public class SyncUserServiceEx implements ISyncUserService {
+    /**
+     * 替换生成平台用户的用户名为人员工号
+     * SyncParamModel的属性 UserParam,是同步到平台的入参
+     * UserParam.getUserParam()是参数的具体入参,修改dataMap 的值put替换标品的就可以 SyncParamModel 里面有自然人信息可以用来查询
+     * @param syncParamModel
+     */
+    @Override
+    public void updateSyncUserParam(SyncParamModel syncParamModel) {
+        UserParam userParam = syncParamModel.getUserParam();
+        Map<String, Object> dataMap = userParam.getDataMap();
+        if(!ObjectUtils.isEmpty(dataMap)) {
+            String number = syncParamModel.getPersonDy().getString(BaseFieldConst.NUMBER);   //人员工号
+
+            //查询带有标识的人员
+            QFilter qFilter = QFilterUtils.getBaseDataQFilter();
+            qFilter.and(new QFilter(BaseFieldConst.NUMBER, QCP.equals,number));
+            DynamicObject bosUsers = BusinessDataServiceHelper.loadSingle("bos_user",qFilter.toArray());
+
+            DynamicObjectCollection collection = bosUsers.getDynamicObjectCollection(BaseFieldConst.ENTRY_ENTITY);
+            //遍历entryentity
+            ArrayList<DynamicObject> list = new ArrayList<>();
+            for (DynamicObject dynamicObject : collection) {
+                //判断dynamicObject内的字段position是否有值,有值则用list集合添加
+                if(dynamicObject.getString("position") != null) {
+                    list.add(dynamicObject);
+                }
+            }
+//            if (!list.isEmpty()){
+            if("C006323".equals(number)) {
+                //如果符合条件,添加分录
+                ArrayList<HashMap<String,Object>> entryEntity = (ArrayList<HashMap<String, Object>>) dataMap.get("entryentity");
+                HashMap<String,Object> addMap = new HashMap<>();
+                for(DynamicObject entry : list){
+                    addMap.put("position",entry.getString("position"));
+                    addMap.put("ispartjob","1");
+                    addMap.put("dpt",entry.getLong("dpt.id"));
+                    addMap.put("superior",entry.getLong("superior.id"));
+                    addMap.put("isincharge",entry.getBoolean("isincharge"));
+                    entryEntity.add(addMap);
+                }
+                dataMap.put("entryentity", entryEntity);
+            }
+        }
+    }
+}

+ 794 - 0
nckd-hr/src/main/java/nckd/yanye/hr/report/gzbb/AttachFileUtils.java

@@ -0,0 +1,794 @@
+
+package nckd.yanye.hr.report.gzbb;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import kd.bos.cache.CacheFactory;
+import kd.bos.cache.TempFileCache;
+import kd.bos.context.RequestContext;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.dataentity.resource.ResManager;
+import kd.bos.dataentity.utils.StringUtils;
+import kd.bos.db.tx.TX;
+import kd.bos.db.tx.TXHandle;
+import kd.bos.entity.EntityMetadataCache;
+import kd.bos.entity.MainEntityType;
+import kd.bos.fileservice.BatchDownloadRequest;
+import kd.bos.fileservice.FileItem;
+import kd.bos.fileservice.FileService;
+import kd.bos.fileservice.FileServiceFactory;
+import kd.bos.fileservice.extension.FileServiceExtFactory;
+import kd.bos.id.ID;
+import kd.bos.logging.BizLog;
+import kd.bos.org.utils.DynamicObjectUtils;
+import kd.bos.orm.query.QFilter;
+import kd.bos.orm.util.CollectionUtils;
+import kd.bos.servicehelper.AttachmentServiceHelper;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.MetadataServiceHelper;
+import kd.bos.servicehelper.operation.SaveServiceHelper;
+import kd.bos.util.FileNameUtils;
+
+
+public class AttachFileUtils {
+    private static String BIDDOCUMENT_EDIT = "_biddocument_edit";
+    private static String COM_ATTACHMENTPANEL = "com_attachmentpanel";
+    private static String TECH_ATTACHMENTPANELAP = "tech_attachmentpanelap";
+    private static String ATTACHMENTPANEL = "attachmentpanel";
+
+    public AttachFileUtils() {
+    }
+
+    public static void copyBidDocumentFileToBill(String appId, Object sourceBillId, String sourceAttachKey, String destinationBillType, Object destinationBillId) {
+        if (StringUtils.equals("all", sourceAttachKey)) {
+            copyFileFromAToB(appId + BIDDOCUMENT_EDIT, sourceBillId, ATTACHMENTPANEL, destinationBillType, destinationBillId, ATTACHMENTPANEL);
+            copyFileFromAToB(appId + BIDDOCUMENT_EDIT, sourceBillId, TECH_ATTACHMENTPANELAP, destinationBillType, destinationBillId, ATTACHMENTPANEL);
+            copyFileFromAToB(appId + BIDDOCUMENT_EDIT, sourceBillId, COM_ATTACHMENTPANEL, destinationBillType, destinationBillId, ATTACHMENTPANEL);
+        } else if (StringUtils.equals(ATTACHMENTPANEL, sourceAttachKey)) {
+            copyFileFromAToB(appId + BIDDOCUMENT_EDIT, sourceBillId, ATTACHMENTPANEL, destinationBillType, destinationBillId, ATTACHMENTPANEL);
+            copyFileFromAToB(appId + BIDDOCUMENT_EDIT, sourceBillId, TECH_ATTACHMENTPANELAP, destinationBillType, destinationBillId, ATTACHMENTPANEL);
+            copyFileFromAToB(appId + BIDDOCUMENT_EDIT, sourceBillId, COM_ATTACHMENTPANEL, destinationBillType, destinationBillId, ATTACHMENTPANEL);
+        }
+
+    }
+
+    public static void copyBidDocumentFileToBillExpComAndTech(String appId, Object sourceBillId, String sourceAttachKey, String destinationBillType, Object destinationBillId) {
+        if (StringUtils.equals("all", sourceAttachKey)) {
+            copyFileFromAToB(appId + BIDDOCUMENT_EDIT, sourceBillId, ATTACHMENTPANEL, destinationBillType, destinationBillId, ATTACHMENTPANEL);
+            copyFileFromAToB(appId + BIDDOCUMENT_EDIT, sourceBillId, TECH_ATTACHMENTPANELAP, destinationBillType, destinationBillId, TECH_ATTACHMENTPANELAP);
+            copyFileFromAToB(appId + BIDDOCUMENT_EDIT, sourceBillId, COM_ATTACHMENTPANEL, destinationBillType, destinationBillId, COM_ATTACHMENTPANEL);
+        } else if (StringUtils.equals(ATTACHMENTPANEL, sourceAttachKey)) {
+            copyFileFromAToB(appId + BIDDOCUMENT_EDIT, sourceBillId, ATTACHMENTPANEL, destinationBillType, destinationBillId, ATTACHMENTPANEL);
+        } else if (StringUtils.equals(TECH_ATTACHMENTPANELAP, sourceAttachKey)) {
+            copyFileFromAToB(appId + BIDDOCUMENT_EDIT, sourceBillId, TECH_ATTACHMENTPANELAP, destinationBillType, destinationBillId, TECH_ATTACHMENTPANELAP);
+        } else if (StringUtils.equals(COM_ATTACHMENTPANEL, sourceAttachKey)) {
+            copyFileFromAToB(appId + BIDDOCUMENT_EDIT, sourceBillId, COM_ATTACHMENTPANEL, destinationBillType, destinationBillId, COM_ATTACHMENTPANEL);
+        }
+
+    }
+
+    public static void synFileFromAToB(String sourceBillType, Object sourceBillId, String sourceAttachKey, String destinationBillType, Object destinationBillId, String destinationAttachKey) {
+        List<Map<String, Object>> sourceFileList = AttachmentServiceHelper.getAttachments(sourceBillType, sourceBillId, sourceAttachKey);
+        List<Object> sourceFileIdList = new ArrayList(50);
+        Iterator var8 = sourceFileList.iterator();
+
+        while(var8.hasNext()) {
+            Map<String, Object> sourceAttach = (Map)var8.next();
+            sourceFileIdList.add(sourceAttach.get("uid"));
+        }
+
+        List<Map<String, Object>> destinationFileList = AttachmentServiceHelper.getAttachments(destinationBillType, destinationBillId, destinationAttachKey);
+        Iterator var12 = destinationFileList.iterator();
+
+        while(var12.hasNext()) {
+            Map<String, Object> destinationAttach = (Map)var12.next();
+            if (!sourceFileIdList.contains(destinationAttach.get("uid"))) {
+                AttachmentServiceHelper.remove(destinationBillType, destinationBillId, destinationAttach.get("uid"));
+            }
+        }
+
+        copyFileFromAToB(sourceBillType, sourceBillId, sourceAttachKey, destinationBillType, destinationBillId, destinationAttachKey);
+    }
+
+    public static void copyFileFromAToB(String sourceBillType, Object sourceBillId, String sourceAttachKey, String destinationBillType, Object destinationBillId, String destinationAttachKey) {
+        List<Map<String, Object>> sourceFileList = AttachmentServiceHelper.getAttachments(sourceBillType, sourceBillId, sourceAttachKey);
+        copyFileToBillByPk(destinationBillType, destinationBillId, destinationAttachKey, sourceFileList);
+    }
+
+    public static void removeAllFile(String billType, Object billId, String attachKey) {
+        List<Map<String, Object>> fileList = AttachmentServiceHelper.getAttachments(billType, billId, attachKey);
+        Iterator var4 = fileList.iterator();
+
+        while(var4.hasNext()) {
+            Map<String, Object> sourceAttach = (Map)var4.next();
+            AttachmentServiceHelper.remove(billType, billId, sourceAttach.get("uid"));
+        }
+
+    }
+
+    public static void copyFileToBillByPk(String formId, Object pkId, String attachKey, List<Map<String, Object>> attachments) {
+        if (pkId != null) {
+            pkId = "" + pkId;
+        }
+
+        List<DynamicObject> attachmentFileList = new ArrayList();
+        MainEntityType mainType = EntityMetadataCache.getDataEntityType("bos_attachment");
+        FileService fileService = FileServiceFactory.getAttachmentFileService();
+        Date date = new Date();
+        int i = 1;
+
+        for(Iterator var9 = attachments.iterator(); var9.hasNext(); ++i) {
+            Map<String, Object> attach = (Map)var9.next();
+            Long attPkId = (Long)((Long)attach.get("attPkId"));
+            String uid = String.valueOf(attach.get("uid"));
+            String fileName = String.valueOf(attach.get("name"));
+            Long fileSize = (Long)attach.get("size");
+            String fExtName = (String)attach.get("name");
+            String newUid = String.format("%s-%s-%s", "rc-upload", date.getTime(), i);
+            DynamicObject oldAttachment = BusinessDataServiceHelper.loadSingle(attPkId, "bos_attachment");
+            String fileRelativePath = oldAttachment.getString("FFileId");
+            InputStream in = fileService.getInputStream(fileRelativePath);
+            RequestContext requestContext = RequestContext.get();
+            String appId = "bid";
+            if (formId.startsWith("rebm")) {
+                appId = "rebm";
+            }
+
+            String pathParam = FileNameUtils.getAttachmentFileName(requestContext.getTenantId(), requestContext.getAccountId(), appId, formId, uid, fileName);
+            FileItem fileItem = new FileItem(fileName, pathParam, in);
+            String filePath = fileService.upload(fileItem);
+            if (!StringUtils.isEmpty(filePath)) {
+                DynamicObject newAttachment = new DynamicObject(mainType);
+                newAttachment.set("id", ID.genLongId());
+                newAttachment.set("FNUMBER", newUid);
+                newAttachment.set("FBillType", formId);
+                newAttachment.set("FInterID", pkId);
+                Timestamp lastModified = (Timestamp)attach.get("lastModified");
+                if (lastModified != null) {
+                    newAttachment.set("FModifyTime", new Date(lastModified.getTime()));
+                } else {
+                    newAttachment.set("FModifyTime", new Date());
+                }
+
+                DynamicObject creator = oldAttachment.getDynamicObject("FCREATEMEN");
+                if (creator != null) {
+                    newAttachment.set("FCREATEMEN", creator.getLong("id"));
+                }
+
+                newAttachment.set("fcreatetime", new Date((Long)attach.get("createdate")));
+                newAttachment.set("FaliasFileName", fileName);
+                newAttachment.set("FAttachmentName", fileName);
+                newAttachment.set("FExtName", fExtName != null ? fExtName.substring(fExtName.lastIndexOf(46) + 1) : "");
+                newAttachment.set("FATTACHMENTSIZE", fileSize);
+                newAttachment.set("fattachmentpanel", attachKey);
+                newAttachment.set("fdescription", attach.get("description"));
+                newAttachment.set("FFileId", filePath);
+                attachmentFileList.add(newAttachment);
+            }
+        }
+
+        if (!CollectionUtils.isEmpty(attachmentFileList)) {
+            DynamicObject[] attachmentFileArray = new DynamicObject[attachmentFileList.size()];
+            attachmentFileList.toArray(attachmentFileArray);
+            SaveServiceHelper.save(attachmentFileArray);
+        }
+
+    }
+
+    public static Map<String, List<String>> copyFileFromAToBReturnMap(String sourceBillType, Object sourceBillId, String sourceAttachKey, String destinationBillType, Object destinationBillId, String destinationAttachKey) {
+        List<Map<String, Object>> sourceFileList = AttachmentServiceHelper.getAttachments(sourceBillType, sourceBillId, sourceAttachKey);
+        return copyFileToBillByPkReturnMap(destinationBillType, destinationBillId, destinationAttachKey, sourceFileList);
+    }
+
+    public static Map<String, List<String>> copyFileToBillByPkReturnMap(String formId, Object pkId, String attachKey, List<Map<String, Object>> attachments) {
+        if (pkId != null) {
+            pkId = "" + pkId;
+        }
+
+        Map<String, List<String>> resultMap = new HashMap();
+        List<String> updateUidList = new ArrayList(attachments.size());
+        List<String> addUidList = new ArrayList(attachments.size());
+        List<DynamicObject> attachmentFileList = new ArrayList();
+        FileService fileService = FileServiceFactory.getAttachmentFileService();
+        MainEntityType mainType = EntityMetadataCache.getDataEntityType("bos_attachment");
+        Date date = new Date();
+        int i = 1;
+
+        for(Iterator var12 = attachments.iterator(); var12.hasNext(); ++i) {
+            Map<String, Object> attach = (Map)var12.next();
+            String uid = String.valueOf(attach.get("uid"));
+            String newUid = String.format("%s-%s-%s", "rc-upload", date.getTime(), i);
+            String fileName = String.valueOf(attach.get("name"));
+            Long attPkId = (Long)((Long)attach.get("attPkId"));
+            Long fileSize = (Long)attach.get("size");
+            String fExtName = (String)attach.get("name");
+            DynamicObject oldAttachment = BusinessDataServiceHelper.loadSingle(attPkId, "bos_attachment");
+            String fileRelativePath = oldAttachment.getString("FFileId");
+            InputStream in = fileService.getInputStream(fileRelativePath);
+            RequestContext requestContext = RequestContext.get();
+            String appId = "bid";
+            if (formId.startsWith("rebm")) {
+                appId = "rebm";
+            }
+
+            String pathParam = FileNameUtils.getAttachmentFileName(requestContext.getTenantId(), requestContext.getAccountId(), appId, formId, uid, fileName);
+            FileItem fileItem = new FileItem(fileName, pathParam, in);
+            String filePath = fileService.upload(fileItem);
+            if (!StringUtils.isEmpty(filePath)) {
+                DynamicObject newAttachment = new DynamicObject(mainType);
+                newAttachment.set("id", ID.genLongId());
+                newAttachment.set("FNUMBER", newUid);
+                newAttachment.set("FBillType", formId);
+                newAttachment.set("FInterID", pkId);
+                Timestamp lastModified = (Timestamp)attach.get("lastModified");
+                if (lastModified != null) {
+                    newAttachment.set("FModifyTime", new Date(lastModified.getTime()));
+                } else {
+                    newAttachment.set("FModifyTime", new Date());
+                }
+
+                DynamicObject creator = oldAttachment.getDynamicObject("FCREATEMEN");
+                if (creator != null) {
+                    newAttachment.set("fcreatemen", creator.getLong("id"));
+                }
+
+                newAttachment.set("fcreatetime", new Date((Long)attach.get("createdate")));
+                newAttachment.set("FaliasFileName", fileName);
+                newAttachment.set("FAttachmentName", fileName);
+                newAttachment.set("FExtName", fExtName != null ? fExtName.substring(fExtName.lastIndexOf(46) + 1) : "");
+                newAttachment.set("FATTACHMENTSIZE", fileSize);
+                newAttachment.set("fattachmentpanel", attachKey);
+                newAttachment.set("fdescription", attach.get("description"));
+                newAttachment.set("FFileId", filePath);
+                attachmentFileList.add(newAttachment);
+            }
+
+            attach.put("createdate", attach.get("createdate"));
+            attach.put("creator", attach.get("creator"));
+        }
+
+        if (!CollectionUtils.isEmpty(attachmentFileList)) {
+            DynamicObject[] attachmentFileArray = new DynamicObject[attachmentFileList.size()];
+            attachmentFileList.toArray(attachmentFileArray);
+            SaveServiceHelper.save(attachmentFileArray);
+        }
+
+        resultMap.put("updateUidList", updateUidList);
+        resultMap.put("addUidList", addUidList);
+        return resultMap;
+    }
+
+    public static void removeSpecificFile(String formId, Object pkId, List<String> uidList) {
+        Iterator var3 = uidList.iterator();
+
+        while(var3.hasNext()) {
+            String uid = (String)var3.next();
+            AttachmentServiceHelper.remove(formId, pkId, uid);
+        }
+
+    }
+
+    public static List<DynamicObject> copyToEntryAttachment(List<Map<String, Object>> attachments) {
+        List<DynamicObject> attachmentFileList = new ArrayList();
+        if (CollectionUtils.isEmpty(attachments)) {
+            return attachmentFileList;
+        } else {
+            try {
+                FileService fileService = FileServiceFactory.getAttachmentFileService();
+                Date now = new Date();
+                Iterator var4 = attachments.iterator();
+
+                while(var4.hasNext()) {
+                    Map<String, Object> attachment = (Map)var4.next();
+                    String fileName = String.valueOf(attachment.get("name"));
+                    String type = fileName != null ? fileName.substring(fileName.lastIndexOf(46) + 1) : "";
+                    DynamicObject oldAttachment = BusinessDataServiceHelper.loadSingle(attachment.get("attPkId"), "bos_attachment");
+                    String fileRelativePath = oldAttachment.getString("FFileId");
+                    InputStream in = fileService.getInputStream(fileRelativePath);
+                    RequestContext requestContext = RequestContext.get();
+                    String pathParam = FileNameUtils.getAttachmentFileName(requestContext.getTenantId(), requestContext.getAccountId(), ID.genLongId(), fileName);
+                    FileItem fileItem = new FileItem(fileName, pathParam, in);
+                    String filePath = fileService.upload(fileItem);
+                    if (!StringUtils.isEmpty(filePath)) {
+                        DynamicObject newAttachment = new DynamicObject(MetadataServiceHelper.getDataEntityType("bd_attachment"));
+                        Long id = ID.genLongId();
+                        newAttachment.set("name", fileName);
+                        newAttachment.set("size", attachment.get("size"));
+                        newAttachment.set("url", filePath);
+                        newAttachment.set("type", type);
+                        newAttachment.set("description", "");
+                        newAttachment.set("tempfile", "1");
+                        newAttachment.set("pageid", "");
+                        newAttachment.set("number", id.toString());
+                        newAttachment.set("status", "B");
+                        newAttachment.set("createtime", now.getTime());
+                        newAttachment.set("modifytime", now.getTime());
+                        newAttachment.set("creator", BusinessDataServiceHelper.loadSingle(Long.parseLong(RequestContext.get().getUserId()), "bos_user"));
+                        newAttachment.set("uid", id.toString());
+                        newAttachment.set("id", id);
+                        attachmentFileList.add(newAttachment);
+                    }
+                }
+
+                TXHandle h = TX.requiresNew();
+                Throwable var31 = null;
+
+                try {
+                    try {
+                        SaveServiceHelper.save((DynamicObject[])attachmentFileList.toArray(new DynamicObject[0]));
+                    } catch (Exception var26) {
+                        h.markRollback();
+                        BizLog.log(var26.getMessage());
+                    }
+                } catch (Throwable var27) {
+                    var31 = var27;
+                    throw var27;
+                } finally {
+                    if (h != null) {
+                        if (var31 != null) {
+                            try {
+                                h.close();
+                            } catch (Throwable var25) {
+                                var31.addSuppressed(var25);
+                            }
+                        } else {
+                            h.close();
+                        }
+                    }
+
+                }
+
+                return attachmentFileList;
+            } catch (Exception var29) {
+                BizLog.log(var29.getMessage());
+                return null;
+            }
+        }
+    }
+
+    public static DynamicObject copyBdAttachment(DynamicObject attachment) {
+        if (attachment == null) {
+            return null;
+        } else {
+            try {
+                MainEntityType mainType = EntityMetadataCache.getDataEntityType("bd_attachment");
+                FileService fileService = FileServiceFactory.getAttachmentFileService();
+                String fileName = attachment.getString("name");
+                String fileRelativePath = attachment.getString("url");
+                InputStream in = fileService.getInputStream(fileRelativePath);
+                RequestContext requestContext = RequestContext.get();
+                String pathParam = FileNameUtils.getAttachmentFileName(requestContext.getTenantId(), requestContext.getAccountId(), ID.genLongId(), fileName);
+                FileItem fileItem = new FileItem(fileName, pathParam, in);
+                String filePath = fileService.upload(fileItem);
+                if (!StringUtils.isEmpty(filePath)) {
+                    Long id = ID.genLongId();
+                    DynamicObject newAttachment = new DynamicObject(mainType);
+                    DynamicObjectUtils.copy(attachment, newAttachment);
+                    newAttachment.set("id", id);
+                    newAttachment.set("number", id.toString());
+                    newAttachment.set("uid", id.toString());
+                    newAttachment.set("url", filePath);
+                    SaveServiceHelper.save(new DynamicObject[]{newAttachment});
+                    return newAttachment;
+                } else {
+                    return null;
+                }
+            } catch (Exception var12) {
+                BizLog.log(var12.getMessage());
+                return null;
+            }
+        }
+    }
+
+    public static void entryToPanelAttachment(List<DynamicObject> attachments, String formId, Object pkId, String attachKey) {
+        MainEntityType mainType = EntityMetadataCache.getDataEntityType("bos_attachment");
+        FileService fileService = FileServiceFactory.getAttachmentFileService();
+        Date date = new Date();
+        int i = 1;
+        List<DynamicObject> attachmentFileList = new ArrayList();
+
+        for(Iterator var9 = attachments.iterator(); var9.hasNext(); ++i) {
+            DynamicObject attach = (DynamicObject)var9.next();
+            String fileName = String.valueOf(attach.get("name"));
+            String newUid = String.format("%s-%s-%s", "rc-upload", date.getTime(), i);
+            String fileRelativePath = attach.getString("url");
+            InputStream in = fileService.getInputStream(fileRelativePath);
+            RequestContext requestContext = RequestContext.get();
+            String pathParam = FileNameUtils.getAttachmentFileName(requestContext.getTenantId(), requestContext.getAccountId(), formId, formId.split("_")[0], newUid, fileName);
+            FileItem fileItem = new FileItem(fileName, pathParam, in);
+            String filePath = fileService.upload(fileItem);
+            if (!StringUtils.isEmpty(filePath)) {
+                DynamicObject newAttachment = new DynamicObject(mainType);
+                newAttachment.set("id", ID.genLongId());
+                newAttachment.set("FNUMBER", newUid);
+                newAttachment.set("FBillType", formId);
+                newAttachment.set("FInterID", pkId);
+                newAttachment.set("FModifyTime", attach.get("modifytime"));
+                DynamicObject creator = attach.getDynamicObject("creator");
+                if (creator != null) {
+                    newAttachment.set("FCREATEMEN", creator.getLong("id"));
+                }
+
+                newAttachment.set("fcreatetime", attach.get("createtime"));
+                newAttachment.set("FaliasFileName", fileName);
+                newAttachment.set("FAttachmentName", fileName);
+                newAttachment.set("FExtName", fileName != null ? fileName.substring(fileName.lastIndexOf(46) + 1) : "");
+                newAttachment.set("FATTACHMENTSIZE", attach.get("size"));
+                newAttachment.set("fattachmentpanel", attachKey);
+                newAttachment.set("fdescription", attach.get("description"));
+                newAttachment.set("FFileId", filePath);
+                attachmentFileList.add(newAttachment);
+            }
+        }
+
+        if (!CollectionUtils.isEmpty(attachmentFileList)) {
+            DynamicObject[] attachmentFileArray = new DynamicObject[attachmentFileList.size()];
+            attachmentFileList.toArray(attachmentFileArray);
+            SaveServiceHelper.save(attachmentFileArray);
+        }
+
+    }
+
+    public static String downloadByAttachmentIds(String fileName, ArrayList<Long> attachmentIds) {
+        QFilter qFilter = new QFilter("id", "in", attachmentIds);
+        DynamicObject[] attachments = BusinessDataServiceHelper.load("bd_attachment", "id,url", new QFilter[]{qFilter});
+        String[] filePaths = (String[])Arrays.stream(attachments).map((attachment) -> {
+            return FileServiceExtFactory.getAttachFileServiceExt().getRealPath(attachment.getString("url"));
+        }).toArray((x$0) -> {
+            return new String[x$0];
+        });
+        return downloadByAttachmentUrls(fileName, filePaths);
+    }
+
+    public static String downloadByAttachmentUrls(String fileName, String... attachmentUrls) {
+        if (attachmentUrls != null && attachmentUrls.length != 0) {
+            if (attachmentUrls.length > 0) {
+                fileName = fileName == null ? "attachments.zip" : (!fileName.toLowerCase(Locale.ENGLISH).endsWith(".zip") ? fileName + ".zip" : fileName);
+            } else if (fileName == null) {
+                String attachmentUrl = attachmentUrls[0];
+                if (attachmentUrl.lastIndexOf(47) != -1) {
+                    int i = attachmentUrl.lastIndexOf(47);
+                    fileName = attachmentUrl.substring(i + 1);
+                }
+            }
+
+            FileService fs = FileServiceFactory.getAttachmentFileService();
+            String userAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.109 Safari/537.36";
+
+            try {
+                ByteArrayOutputStream out = new ByteArrayOutputStream();
+                Throwable var5 = null;
+
+                String attachmentUrl;
+                try {
+                    if (attachmentUrls.length > 0) {
+                        BatchDownloadRequest bdr = getBatchDownloadRequest(attachmentUrls);
+                        fs.batchDownload(bdr, out, userAgent);
+                    } else {
+                        attachmentUrl = attachmentUrls[0];
+                        fs.download(attachmentUrl, out, userAgent);
+                    }
+
+                    out.flush();
+                    attachmentUrl = download(fileName, out.toByteArray());
+                } catch (Throwable var16) {
+                    var5 = var16;
+                    throw var16;
+                } finally {
+                    if (out != null) {
+                        if (var5 != null) {
+                            try {
+                                out.close();
+                            } catch (Throwable var15) {
+                                var5.addSuppressed(var15);
+                            }
+                        } else {
+                            out.close();
+                        }
+                    }
+
+                }
+
+                return attachmentUrl;
+            } catch (Exception var18) {
+                return var18.getMessage();
+            }
+        } else {
+            return null;
+        }
+    }
+
+    private static BatchDownloadRequest getBatchDownloadRequest(String[] attachmentUrls) {
+        BatchDownloadRequest.Dir srcDir = new BatchDownloadRequest.Dir("attachments");
+        List<BatchDownloadRequest.File> srcFiles = new ArrayList(attachmentUrls.length);
+        String[] var3 = attachmentUrls;
+        int var4 = attachmentUrls.length;
+
+        for(int var5 = 0; var5 < var4; ++var5) {
+            String attachmentUrl = var3[var5];
+            if (attachmentUrl.lastIndexOf(47) != -1) {
+                int i = attachmentUrl.lastIndexOf(47);
+                String attachmentName = attachmentUrl.substring(i + 1);
+                srcFiles.add(new BatchDownloadRequest.File(attachmentName, attachmentUrl));
+            }
+        }
+
+        srcDir.setFiles((BatchDownloadRequest.File[])srcFiles.toArray(new BatchDownloadRequest.File[0]));
+        BatchDownloadRequest bdr = new BatchDownloadRequest("test-batch-download");
+        bdr.setDirs(new BatchDownloadRequest.Dir[]{srcDir});
+        return bdr;
+    }
+
+    public static String download(String fileName, byte[] bytes) {
+        InputStream in = new ByteArrayInputStream(bytes);
+        return download(fileName, (InputStream)in);
+    }
+
+    public static String download(String fileName, InputStream in) {
+        TempFileCache cache = CacheFactory.getCommonCacheFactory().getTempFileCache();
+        return cache.saveAsUrl(fileName, in, 120);
+    }
+
+    public static String downAllFile(String fileName, Object formId, String formBillno, String appid, String evalType) {
+        fileName = fileName == null ? "attachments.zip" : (!fileName.toLowerCase(Locale.ENGLISH).endsWith(".zip") ? fileName + ".zip" : fileName);
+        FileService fs = FileServiceFactory.getAttachmentFileService();
+        String userAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.109 Safari/537.36";
+
+        try {
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            Throwable var8 = null;
+
+            String var10;
+            try {
+                BatchDownloadRequest bdr = getBatchFile(formId, formBillno, appid, evalType);
+                fs.batchDownload(bdr, out, userAgent);
+                out.flush();
+                var10 = download(fileName, out.toByteArray());
+            } catch (Throwable var20) {
+                var8 = var20;
+                throw var20;
+            } finally {
+                if (out != null) {
+                    if (var8 != null) {
+                        try {
+                            out.close();
+                        } catch (Throwable var19) {
+                            var8.addSuppressed(var19);
+                        }
+                    } else {
+                        out.close();
+                    }
+                }
+
+            }
+
+            return var10;
+        } catch (Exception var22) {
+            return var22.getMessage();
+        }
+    }
+
+    private static BatchDownloadRequest getBatchFile(Object formId, String formBillno, String appid, String evalType) {
+        String secTionBillNo = "";
+        String secTionNameKey = "";
+        String isDarkTag = "";
+        String supplierDarkTag = "";
+        if (formBillno.contains("onlinebideval")) {
+            secTionBillNo = "bidsectionenroll";
+            secTionNameKey = "sectionname2";
+            isDarkTag = "isdark";
+            supplierDarkTag = "supplierdarkname_entry";
+        } else if (formBillno.contains("decision")) {
+            secTionBillNo = "bidsection";
+            secTionNameKey = "sectionname";
+        } else if (formBillno.contains("bidopen")) {
+            secTionBillNo = "bidsection";
+            secTionNameKey = "sectionname";
+            isDarkTag = "isdark";
+            supplierDarkTag = "darksuppliername";
+        }
+
+        List<BatchDownloadRequest.Dir> listDir = new ArrayList();
+        DynamicObject dynamicObject = BusinessDataServiceHelper.loadSingle(formId, formBillno);
+        boolean isDark = false;
+        if (!formBillno.contains("decision")) {
+            isDark = dynamicObject.getBoolean(isDarkTag);
+        }
+
+        DynamicObject bidProject = dynamicObject.getDynamicObject("bidproject");
+        DynamicObjectCollection bidsectionenrollCol = dynamicObject.getDynamicObjectCollection(secTionBillNo);
+        Iterator var14 = bidsectionenrollCol.iterator();
+
+        while(var14.hasNext()) {
+            DynamicObject bidsectionenroll = (DynamicObject)var14.next();
+            String sectionName = bidsectionenroll.getString(secTionNameKey);
+            DynamicObjectCollection supplierentryCol = bidsectionenroll.getDynamicObjectCollection("supplierentry");
+            Iterator var18 = supplierentryCol.iterator();
+
+            while(var18.hasNext()) {
+                DynamicObject supplierentry = (DynamicObject)var18.next();
+                DynamicObject supplier = supplierentry.getDynamicObject("supplier");
+                String supplierName;
+                if (isDark) {
+                    supplierName = supplierentry.getString(supplierDarkTag);
+                } else {
+                    supplierName = supplier.getString("name");
+                }
+
+                String fileZipName = getFileZipName(bidProject.getPkValue(), sectionName, supplierName, formBillno, formId, appid);
+                BatchDownloadRequest.Dir srcDir = new BatchDownloadRequest.Dir(fileZipName);
+                ArrayList<Long> attachmentIds = new ArrayList();
+                getFileUrl(supplierentry, evalType, attachmentIds, formBillno, bidProject.getPkValue());
+                List<BatchDownloadRequest.File> srcFiles = new ArrayList(attachmentIds.size());
+                QFilter qFilter = new QFilter("id", "in", attachmentIds);
+                DynamicObject[] attachments = BusinessDataServiceHelper.load("bd_attachment", "id,url", new QFilter[]{qFilter});
+                String[] filePaths = (String[])Arrays.stream(attachments).map((attachment) -> {
+                    return FileServiceExtFactory.getAttachFileServiceExt().getRealPath(attachment.getString("url"));
+                }).toArray((x$0) -> {
+                    return new String[x$0];
+                });
+                String[] var28 = filePaths;
+                int var29 = filePaths.length;
+
+                for(int var30 = 0; var30 < var29; ++var30) {
+                    String attachmentUrl = var28[var30];
+                    if (attachmentUrl.lastIndexOf(47) != -1) {
+                        int i = attachmentUrl.lastIndexOf(47);
+                        String attachmentName = attachmentUrl.substring(i + 1);
+                        srcFiles.add(new BatchDownloadRequest.File(attachmentName, attachmentUrl));
+                    }
+                }
+
+                srcDir.setFiles((BatchDownloadRequest.File[])srcFiles.toArray(new BatchDownloadRequest.File[0]));
+                listDir.add(srcDir);
+            }
+        }
+
+        BatchDownloadRequest bdr = new BatchDownloadRequest("test-batch-download");
+        bdr.setDirs((BatchDownloadRequest.Dir[])listDir.toArray(new BatchDownloadRequest.Dir[0]));
+        return bdr;
+    }
+
+    public static String getFileZipName(Object bidprojectId, String sectionName, String supplierName, String formBillno, Object formId, String appid) {
+        return fileZipName(bidprojectId, sectionName, supplierName, formBillno, formId, appid);
+    }
+
+    public static void getFileUrl(DynamicObject supplierEntry, String evalType, ArrayList<Long> attachmentIds, String formBillno, Object bidprojectId) {
+        String tecName = "";
+        String comName = "";
+        if (formBillno.contains("onlinebideval")) {
+            tecName = "supplier_techattach";
+            comName = "supplier_comattach";
+        } else if (formBillno.contains("decision")) {
+            tecName = "techfile_new";
+            comName = "commfile_new";
+        } else if (formBillno.contains("bidopen")) {
+            tecName = "supplier_techattach";
+            comName = "supplier_comattach";
+        }
+
+        DynamicObjectCollection supplierTechattachCol;
+        if ("TECHNICAL".equals(evalType)) {
+            supplierTechattachCol = supplierEntry.getDynamicObjectCollection(tecName);
+            if (!supplierTechattachCol.isEmpty()) {
+                supplierTechattachCol.forEach((attachment) -> {
+                    DynamicObject fileFbasedataid = attachment.getDynamicObject("fbasedataid");
+                    if (fileFbasedataid != null) {
+                        Long o = (Long)fileFbasedataid.get("id");
+                        attachmentIds.add(o);
+                    }
+
+                });
+            }
+        } else if ("BUSSINESS".equals(evalType)) {
+            supplierTechattachCol = supplierEntry.getDynamicObjectCollection(comName);
+            if (!supplierTechattachCol.isEmpty()) {
+                supplierTechattachCol.forEach((attachment) -> {
+                    DynamicObject fileFbasedataid = attachment.getDynamicObject("fbasedataid");
+                    if (fileFbasedataid != null) {
+                        Long o = (Long)fileFbasedataid.get("id");
+                        attachmentIds.add(o);
+                    }
+
+                });
+            }
+        } else if ("MULTI".equals(evalType)) {
+            supplierTechattachCol = supplierEntry.getDynamicObjectCollection(tecName);
+            if (!supplierTechattachCol.isEmpty()) {
+                supplierTechattachCol.forEach((attachment) -> {
+                    DynamicObject fileFbasedataid = attachment.getDynamicObject("fbasedataid");
+                    if (fileFbasedataid != null) {
+                        Long o = (Long)fileFbasedataid.get("id");
+                        attachmentIds.add(o);
+                    }
+
+                });
+            }
+
+            DynamicObjectCollection supplierComattachCol = supplierEntry.getDynamicObjectCollection(comName);
+            if (!supplierComattachCol.isEmpty()) {
+                supplierComattachCol.forEach((attachment) -> {
+                    DynamicObject fileFbasedataid = attachment.getDynamicObject("fbasedataid");
+                    if (fileFbasedataid != null) {
+                        Long o = (Long)fileFbasedataid.get("id");
+                        attachmentIds.add(o);
+                    }
+
+                });
+            }
+        }
+
+        String entityName = "bid_project";
+        if (formBillno.contains("rebm")) {
+            entityName = "rebm_project";
+        }
+
+        if (formBillno.contains("bidopen")) {
+            DynamicObject bidProject = BusinessDataServiceHelper.loadSingle(bidprojectId, entityName);
+            String doctype = bidProject.getString("doctype");
+            if ("BUSSINESS".equals(doctype) || "TECHNICAL".equals(evalType) || "MULTI".equals(evalType)) {
+                DynamicObjectCollection tenAttach = supplierEntry.getDynamicObjectCollection("supplier_tenattach");
+                DynamicObjectCollection otherAttach = supplierEntry.getDynamicObjectCollection("supplier_otherattach");
+                if (!tenAttach.isEmpty()) {
+                    tenAttach.forEach((attachment) -> {
+                        DynamicObject fileFbasedataid = attachment.getDynamicObject("fbasedataid");
+                        if (fileFbasedataid != null) {
+                            Long o = (Long)fileFbasedataid.get("id");
+                            attachmentIds.add(o);
+                        }
+
+                    });
+                }
+
+                if (!otherAttach.isEmpty()) {
+                    otherAttach.forEach((attachment) -> {
+                        DynamicObject fileFbasedataid = attachment.getDynamicObject("fbasedataid");
+                        if (fileFbasedataid != null) {
+                            Long o = (Long)fileFbasedataid.get("id");
+                            attachmentIds.add(o);
+                        }
+
+                    });
+                }
+            }
+        }
+
+    }
+
+    public static String fileZipName(Object bidprojectId, String sectionName, String supplierName, String formBillno, Object formId, String appid) {
+        String projectName;
+        if ("bid".equals(appid)) {
+            projectName = "bid_project";
+        } else {
+            projectName = "rebm_project";
+        }
+
+        DynamicObject bidProject = BusinessDataServiceHelper.loadSingle(bidprojectId, projectName);
+        String billnoProject = bidProject.getString("billno");
+        if (sectionName.length() > 20) {
+            sectionName = sectionName.substring(0, 20);
+        }
+
+        String formName = formBillno;
+        String fileZipName = "";
+        fileZipName = billnoProject + sectionName + supplierName + formName;
+        fileZipName = String.format(ResManager.loadKDString("%s附件", "BidFileDownLoadNameUtil_0", "scm-bid-common", new Object[0]), fileZipName);
+        if (fileZipName.length() > 255) {
+            fileZipName = formName.substring(0, 255);
+        }
+
+        return fileZipName;
+    }
+
+}

+ 437 - 0
nckd-hr/src/main/java/nckd/yanye/hr/report/gzbb/PushSjcjDataUtils.java

@@ -0,0 +1,437 @@
+package nckd.yanye.hr.report.gzbb;
+
+import kd.bos.context.RequestContext;
+import kd.bos.dataentity.entity.CloneUtils;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.dataentity.utils.StringUtils;
+import kd.bos.login.utils.DateUtils;
+import kd.bos.message.api.MessageChannels;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.AttachmentServiceHelper;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.QueryServiceHelper;
+import kd.bos.servicehelper.coderule.CodeRuleServiceHelper;
+import kd.bos.servicehelper.operation.SaveServiceHelper;
+import kd.bos.servicehelper.workflow.MessageCenterServiceHelper;
+import kd.bos.web.actions.utils.FilePathUtil;
+import kd.bos.workflow.engine.msg.info.MessageInfo;
+
+import java.io.IOException;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**数据采集单据工具类*/
+public class PushSjcjDataUtils {
+
+    /**推送消息*/
+    public static void savePushBillMsg (DynamicObject dynamicObject, List<Long> receivers) {
+        Date nckd_ssny = dynamicObject.getDate("nckd_ssny");// 所属年月
+        String ssnyStr = DateUtils.formatDate(nckd_ssny, "yyyy年MM月");
+        Date nckd_tbjzsj = dynamicObject.getDate("nckd_tbjzsj");// 填报截止时间
+        String nckd_tbjzsjStr = DateUtils.formatDate(nckd_tbjzsj, "yyyy年MM月dd日");
+        String billname = dynamicObject.getString("nckd_billname");// 任务名称
+        String title = billname+"填报通知";
+        String content = "请尽快完成"+ssnyStr+"的数据填报任务,截止时间为:"+nckd_tbjzsjStr;
+        String senderName = RequestContext.get().getUserName();
+        MessageInfo message = new MessageInfo();
+        message.setType(MessageInfo.TYPE_MESSAGE);//推送消息的类型
+        StringBuilder notifyType = new StringBuilder();
+        notifyType.append(MessageChannels.MC);
+        notifyType.append(MessageChannels.YUNZHIJIA);
+        message.setNotifyType(notifyType.toString());
+        message.setTitle(title);
+        message.setUserIds(receivers);//接收用户
+        message.setSenderName(senderName);
+        message.setContent(content);//详情消息描述
+        MessageCenterServiceHelper.sendMessage(message);//推送消息
+    }
+
+    /**推送消息*/
+    public static void savePushDelBillMsg (DynamicObject dynamicObject, List<Long> receivers) {
+        Date nckd_ssny = dynamicObject.getDate("nckd_ssny");// 所属年月
+        String ssnyStr = DateUtils.formatDate(nckd_ssny, "yyyy年MM月");
+        //Date nckd_tbjzsj = dynamicObject.getDate("nckd_tbjzsj");// 填报截止时间
+        String billname = dynamicObject.getString("nckd_billname");// 任务名称
+        String title = billname+"作废通知";
+        String content = ssnyStr+"的数据填报任务已作废";
+        String senderName = RequestContext.get().getUserName();
+        MessageInfo message = new MessageInfo();
+        message.setType(MessageInfo.TYPE_MESSAGE);//推送消息的类型
+        StringBuilder notifyType = new StringBuilder();
+        notifyType.append(MessageChannels.MC);
+        notifyType.append(MessageChannels.YUNZHIJIA);
+        message.setNotifyType(notifyType.toString());
+        message.setTitle(title);
+        message.setUserIds(receivers);//接收用户
+        message.setSenderName(senderName);
+        message.setContent(content);//详情消息描述
+        MessageCenterServiceHelper.sendMessage(message);//推送消息
+    }
+
+    /**推送消息*/
+    public static void savePushCuiBanMsg (DynamicObject dynamicObject, List<Long> receivers) {
+        Date nckd_ssny = dynamicObject.getDate("nckd_tbsjny");// 所属年月
+        String ssnyStr = DateUtils.formatDate(nckd_ssny, "yyyy年MM月");
+        Date nckd_tbjzsj = dynamicObject.getDate("nckd_tbjzrq");// 填报截止时间
+        String nckd_tbjzsjStr = DateUtils.formatDate(nckd_tbjzsj, "yyyy年MM月dd日");
+        String billname = dynamicObject.getString("nckd_rwmc");// 任务名称
+        String title = billname+"催办通知";
+        String content = "请尽快完成"+ssnyStr+"的数据填报任务,截止时间为:"+nckd_tbjzsjStr;
+        String senderName = RequestContext.get().getUserName();
+        MessageInfo message = new MessageInfo();
+        message.setType(MessageInfo.TYPE_MESSAGE);//推送消息的类型
+        StringBuilder notifyType = new StringBuilder();
+        notifyType.append(MessageChannels.MC);
+        notifyType.append(MessageChannels.YUNZHIJIA);
+        message.setNotifyType(notifyType.toString());
+        message.setTitle(title);
+        message.setUserIds(receivers);//接收用户
+        message.setSenderName(senderName);
+        message.setContent(content);//详情消息描述
+        MessageCenterServiceHelper.sendMessage(message);//推送消息
+    }
+
+    /**生成填报任务单据*/
+    public static void savePushBillData(DynamicObject sjcjObject, DynamicObject sjcjObjectEntry) {
+        String billno = sjcjObject.getString("billno");// 数据采集编号
+        Long billid = sjcjObject.getLong("id");// 源单id
+        Date nckd_ssny = sjcjObject.getDate("nckd_ssny");// 所属年月
+        String billname = sjcjObject.getString("nckd_billname");// 任务名称
+        Date nckd_tbkssj = sjcjObject.getDate("nckd_tbkssj");// 填报开始时间
+        Date nckd_tbjzrq = sjcjObject.getDate("nckd_tbjzsj");// 填报截止时间
+        String nckd_sfjtxf = sjcjObject.getString("nckd_sfjtxf");// 是否初始下发
+        String nckd_remark = sjcjObject.getString("nckd_remark");// 描述
+
+        String nckd_dxbbmc = sjcjObjectEntry.getString("nckd_dxbbmc");// 填报报名称
+        DynamicObjectCollection nckd_tztbry = sjcjObjectEntry.getDynamicObjectCollection("nckd_tztbry");// 通知人员
+        DynamicObject nckd_tbxzzz = sjcjObjectEntry.getDynamicObject("nckd_tbxzzz");// 填报组织
+        DynamicObject sjtbTaskObject = BusinessDataServiceHelper.newDynamicObject("nckd_sjtbrw");
+        String sjtbBillNo = CodeRuleServiceHelper.getNumber("nckd_sjtbrw", sjtbTaskObject, null);
+        sjtbTaskObject.set("billno", sjtbBillNo);// 单据编号
+        sjtbTaskObject.set("billstatus", "A");// 暂存
+        sjtbTaskObject.set("nckd_rwmc", billname);
+        sjtbTaskObject.set("org", nckd_tbxzzz.getLong("id"));// 填报组织
+        sjtbTaskObject.set("nckd_bblx", "1");// 报表类型
+        sjtbTaskObject.set("nckd_tbsjny", nckd_ssny);// 数据填报年月
+
+        DynamicObjectCollection userColl = sjtbTaskObject.getDynamicObjectCollection("nckd_zrr");
+        for (DynamicObject user : nckd_tztbry) {
+            DynamicObject userObject = user.getDynamicObject("fbasedataId");
+            DynamicObject newUser = new DynamicObject(userColl.getDynamicObjectType());
+            newUser.set("fbasedataId", userObject);
+            userColl.add(newUser);
+        }
+        sjtbTaskObject.set("nckd_zrr", userColl);// 责任人
+
+        String nckd_ydcbm = billno + "!";// 源单长编码
+        if ("0".equals(nckd_sfjtxf)) {// 非集团下发
+            String nckd_ydcbm_sjcj = sjcjObject.getString("nckd_ydcbm");// 源单长编码
+            nckd_ydcbm = nckd_ydcbm_sjcj + billno + "!";
+        }
+        sjtbTaskObject.set("nckd_ydcbm", nckd_ydcbm);// 源单长编码
+        sjtbTaskObject.set("nckd_ydbh", billno);// 源单编号
+        sjtbTaskObject.set("nckd_ydpk", billid);// 源单ID
+        sjtbTaskObject.set("nckd_ydst", "nckd_sjcjrw");// 源单实体
+        sjtbTaskObject.set("nckd_sfyxscxjrw", "1");// 是否生成的填报任务0否1是
+        sjtbTaskObject.set("nckd_rwzt", "1");// 任务状态,待上报
+        sjtbTaskObject.set("nckd_asb", nckd_remark);// 描述
+        if (nckd_tbkssj != null) {
+            sjtbTaskObject.set("nckd_tbkssj", nckd_tbkssj);// 填报开始时间
+        }
+        sjtbTaskObject.set("nckd_tbjzrq", nckd_tbjzrq);// 填报截止日期
+        if (nckd_dxbbmc.contains(",1,")) {
+            sjtbTaskObject.set("nckd_zsba", "1");// 展示表a
+        } else {
+            sjtbTaskObject.set("nckd_zsba", "0");// 展示表a
+        }
+        if (nckd_dxbbmc.contains(",2,")) {
+            sjtbTaskObject.set("nckd_zsbb", "1");// 展示表b
+        } else {
+            sjtbTaskObject.set("nckd_zsbb", "0");// 展示表b
+        }
+        if (nckd_dxbbmc.contains(",3,")) {
+            sjtbTaskObject.set("nckd_zsbc", "1");// 展示表c
+        } else {
+            sjtbTaskObject.set("nckd_zsbc", "0");// 展示表c
+        }
+        if (nckd_dxbbmc.contains(",4,")) {
+            sjtbTaskObject.set("nckd_zsbd", "1");// 展示表d
+        } else {
+            sjtbTaskObject.set("nckd_zsbd", "0");// 展示表d
+        }
+        if (nckd_dxbbmc.contains(",5,")) {
+            sjtbTaskObject.set("nckd_zsbe", "1");// 展示表e
+        } else {
+            sjtbTaskObject.set("nckd_zsbe", "0");// 展示表e
+        }
+        if (nckd_dxbbmc.contains(",6,")) {
+            sjtbTaskObject.set("nckd_zsbf", "1");// 展示表f
+        } else {
+            sjtbTaskObject.set("nckd_zsbf", "0");// 展示表f
+        }
+        Object[] result = SaveServiceHelper.save(new DynamicObject[]{sjtbTaskObject});
+        DynamicObject dynamicObject = (DynamicObject) result[0];
+        Long sjtbTaskId = dynamicObject.getLong("id");
+        //通过AttachmentServiceHelper.getAttachments(formId, pkId, attachKey)获取源单据附件面板附件数据
+        List<Map<String, Object>> attachmentData = AttachmentServiceHelper.getAttachments("nckd_sjcjrw", billid, "attachmentpanel");
+        if (attachmentData.size() > 0) {
+            //调用AttachmentServiceHelper.upload(formId, pkId, attachKey,  attachments)将附件数据上传到目标附件面板
+            //AttachmentServiceHelper.upload("nckd_sjtbrw", sjtbTaskId, "nckd_atbsmfjmb", attachmentData);
+            AttachFileUtils.copyFileFromAToB("nckd_sjcjrw", billid, "attachmentpanel", "nckd_sjtbrw", sjtbTaskId, "nckd_atbsmfjmb");
+        }
+    }
+
+    /**获取文件服务器相对路径*/
+    public static String getPathfromDownloadUrl(String url) throws IOException {
+        String path = StringUtils.substringAfter(url, "path=");
+        path = URLDecoder.decode(path, "UTF-8");
+        return FilePathUtil.dealPath(path, "attach");
+    }
+
+    /**根据数据采集任务编号作废填报任务单据*/
+    public static void deleteSjtbBillData(DynamicObject sjcjrwObject) {
+        String billno = sjcjrwObject.getString("billno");
+        List<QFilter> qfilter = new ArrayList<QFilter>();
+        qfilter.add(new QFilter("nckd_ydcbm", "like", billno+"!%"));
+        QFilter[] filters = (QFilter[]) qfilter.toArray(new QFilter[qfilter.size()]);
+        DynamicObjectCollection xjtbrwObjects = QueryServiceHelper.query("nckd_sjtbrw", "id,billno", filters);
+        if (xjtbrwObjects.size() > 0) {
+            DynamicObject [] saveDynamicObjects = new DynamicObject[xjtbrwObjects.size()];
+            List<Long> receiverList = new ArrayList<Long>();
+            for (int i = 0; i < xjtbrwObjects.size(); i++) {
+                DynamicObject sjtbrw = xjtbrwObjects.get(i);
+                Long id = sjtbrw.getLong("id");
+                DynamicObject dynamicObject = BusinessDataServiceHelper.loadSingle(id, "nckd_sjtbrw");
+                dynamicObject.set("nckd_rwzt", "3");// 任务状态,作废
+                saveDynamicObjects[i] = dynamicObject;
+                DynamicObjectCollection nckd_zrr = dynamicObject.getDynamicObjectCollection("nckd_zrr");// 责任人
+                List<Long> receivers = nckd_zrr.stream().map(zrrObject -> zrrObject.getLong("fbasedataid.id")).collect(Collectors.toList());
+                receiverList.addAll(receivers);
+            }
+            SaveServiceHelper.save(saveDynamicObjects);
+            PushSjcjDataUtils.savePushDelBillMsg(sjcjrwObject, receiverList);
+        }
+    }
+
+    /**根据数据采集任务编号作废数据采集任务单据*/
+    public static void deleteSjcjBillData(DynamicObject sjcjrwObject) {
+        String billno = sjcjrwObject.getString("billno");
+        List<QFilter> qfilter = new ArrayList<QFilter>();
+        qfilter.add(new QFilter("nckd_ydcbm", "like", billno+"!%"));
+        QFilter[] filters = (QFilter[]) qfilter.toArray(new QFilter[qfilter.size()]);
+        DynamicObjectCollection xjtbrwObjects = QueryServiceHelper.query("nckd_sjcjrw", "id,billno", filters);
+        if (xjtbrwObjects.size() > 0) {
+            DynamicObject [] saveDynamicObjects = new DynamicObject[xjtbrwObjects.size()];
+            List<Long> receiverList = new ArrayList<Long>();
+            for (int i = 0; i < xjtbrwObjects.size(); i++) {
+                DynamicObject sjtbrw = xjtbrwObjects.get(i);
+                Long id = sjtbrw.getLong("id");
+                DynamicObject dynamicObject = BusinessDataServiceHelper.loadSingle(id, "nckd_sjcjrw");
+                dynamicObject.set("billstatus", "A");// 暂存
+                dynamicObject.set("nckd_sjzt", "1");// 已作废
+                saveDynamicObjects[i] = dynamicObject;
+                // 获取任务配置分录
+                DynamicObjectCollection dynamicObjectCollection = dynamicObject.getDynamicObjectCollection("nckd_sjcjrw_entry");
+                for (DynamicObject dynamicObjectEntry: dynamicObjectCollection) {
+                    DynamicObjectCollection nckd_tztbry = dynamicObjectEntry.getDynamicObjectCollection("nckd_tztbry");// 通知人员
+                    List<Long> receivers = nckd_tztbry.stream().map(tztbryObject -> tztbryObject.getLong("fbasedataid.id")).collect(Collectors.toList());
+                    receiverList.addAll(receivers);
+                }
+            }
+            SaveServiceHelper.save(saveDynamicObjects);
+            PushSjcjDataUtils.savePushDelBillMsg(sjcjrwObject, receiverList);
+        }
+    }
+
+    /**合并数据填报任务单据*/
+    public static boolean saveMergeSjtbBill (List<DynamicObject> dynamicObjectList) {
+        // 需要获取到任务名称等信息
+        DynamicObject firstObject = dynamicObjectList.get(0);
+        String nckd_bblx = firstObject.getString("nckd_bblx");// 1填报表2汇总表
+        String firstNckdYdpk = firstObject.getString("nckd_ydpk");// 源单ID
+        String firstNckdYdst = firstObject.getString("nckd_ydst");// 源单实体
+        String firstSourceBillName = "";
+        Long firstSourceOrgId = 0L;
+        Date firstNckdSsny = null;
+        Date firstSourceNckdTbkssj = null;
+        Date firstSourceNckdTbjzsj = null;
+        String firstSourceNckdMs = "";
+        DynamicObject sourceDynamicObject = null;
+        if ("nckd_sjtbrw".equals(firstNckdYdst)) {
+            if ("1".equals(nckd_bblx)) {// 填报表
+                sourceDynamicObject = BusinessDataServiceHelper.loadSingle(Long.valueOf(firstNckdYdpk), "nckd_sjtbrw");
+                firstSourceOrgId = sourceDynamicObject.getLong("org.id");// 填报组织
+                firstSourceBillName = sourceDynamicObject.getString("nckd_rwmc");// 任务名称
+                firstNckdSsny = sourceDynamicObject.getDate("nckd_tbsjny");// 所属年月
+                firstSourceNckdTbkssj = sourceDynamicObject.getDate("nckd_tbkssj");// 填报开始时间
+                firstSourceNckdTbjzsj = sourceDynamicObject.getDate("nckd_tbjzrq");// 填报截止时间
+                firstSourceNckdMs = sourceDynamicObject.getString("nckd_asb");// 描述
+            } else {// 汇总表
+                sourceDynamicObject = dynamicObjectList.get(0);
+                firstSourceOrgId = sourceDynamicObject.getLong("org.id");// 填报组织
+                firstSourceBillName = sourceDynamicObject.getString("nckd_rwmc");// 任务名称
+                firstNckdSsny = sourceDynamicObject.getDate("nckd_tbsjny");// 所属年月
+                firstSourceNckdTbkssj = sourceDynamicObject.getDate("nckd_tbkssj");// 填报开始时间
+                firstSourceNckdTbjzsj = sourceDynamicObject.getDate("nckd_tbjzrq");// 填报截止时间
+                firstSourceNckdMs = sourceDynamicObject.getString("nckd_asb");// 描述
+            }
+        } else {
+            sourceDynamicObject = BusinessDataServiceHelper.loadSingle(Long.valueOf(firstNckdYdpk), "nckd_sjcjrw");
+            firstSourceOrgId = sourceDynamicObject.getLong("org.id");// 人事管理组织
+            firstSourceBillName = sourceDynamicObject.getString("nckd_billname");// 单据名称
+            firstSourceNckdTbkssj = sourceDynamicObject.getDate("nckd_tbkssj");// 填报开始时间
+            firstSourceNckdTbjzsj = sourceDynamicObject.getDate("nckd_tbjzsj");// 填报截止时间
+            firstSourceNckdMs = sourceDynamicObject.getString("nckd_remark");// 描述
+            firstNckdSsny = sourceDynamicObject.getDate("nckd_ssny");// 所属年月
+        }
+
+        // 新建汇总单据
+        DynamicObject sjhzTaskObject = BusinessDataServiceHelper.newDynamicObject("nckd_sjtbrw");
+        String sjhzBillNo = CodeRuleServiceHelper.getNumber("nckd_sjtbrw", sjhzTaskObject, null);
+        sjhzTaskObject.set("billno", sjhzBillNo);// 单据编号
+        sjhzTaskObject.set("billstatus", "A");// 暂存
+        sjhzTaskObject.set("nckd_rwmc", firstSourceBillName);
+        sjhzTaskObject.set("org", firstSourceOrgId);// 填报组织
+        sjhzTaskObject.set("nckd_bblx", "2");// 报表类型
+        sjhzTaskObject.set("nckd_tbsjny", firstNckdSsny);// 数据填报年月
+        DynamicObjectCollection userColl = sjhzTaskObject.getDynamicObjectCollection("nckd_zrr");
+
+        DynamicObjectCollection firstSourceNckdZrr = new DynamicObjectCollection();
+        if ("nckd_sjtbrw".equals(firstNckdYdst)) {
+            firstSourceNckdZrr = sourceDynamicObject.getDynamicObjectCollection("nckd_zrr");// 责任人
+        } else {
+            DynamicObject userObject = sourceDynamicObject.getDynamicObject("creator");
+            //DynamicObject userObject = BusinessDataServiceHelper.loadSingle(userPkId, "bos_user");
+            DynamicObject newUser = new DynamicObject(userColl.getDynamicObjectType());
+            newUser.set("fbasedataId", userObject);
+            firstSourceNckdZrr.add(newUser);
+        }
+        for (DynamicObject user : firstSourceNckdZrr) {
+            DynamicObject userObject = user.getDynamicObject("fbasedataId");
+            DynamicObject newUser = new DynamicObject(userColl.getDynamicObjectType());
+            newUser.set("fbasedataId", userObject);
+            userColl.add(newUser);
+        }
+        sjhzTaskObject.set("nckd_zrr", userColl);// 责任人
+
+        String nckd_ydcbm = sjhzBillNo + "!";// 源单长编码
+        sjhzTaskObject.set("nckd_ydcbm", nckd_ydcbm);// 源单长编码
+        sjhzTaskObject.set("nckd_ydbh", "");// 源单编号
+        sjhzTaskObject.set("nckd_ydpk", "");// 源单ID
+        sjhzTaskObject.set("nckd_ydst", "nckd_sjtbrw");// 源单实体
+        sjhzTaskObject.set("nckd_sfyxscxjrw", "0");// 是否生成的填报任务0否1是
+        sjhzTaskObject.set("nckd_rwzt", "1");// 任务状态,待上报
+        if (firstSourceNckdTbkssj != null) {
+            sjhzTaskObject.set("nckd_tbkssj", firstSourceNckdTbkssj);// 填报开始时间
+        }
+        sjhzTaskObject.set("nckd_tbjzrq", firstSourceNckdTbjzsj);// 填报截止日期
+        sjhzTaskObject.set("nckd_asb", firstSourceNckdMs);// 描述
+        sjhzTaskObject.set("nckd_zsba", "1");// 展示表a
+
+        sjhzTaskObject = saveMergeSjtbBillEntry(sjhzTaskObject, dynamicObjectList);
+        DynamicObject [] saveDynamicObjects = new DynamicObject[1];
+        saveDynamicObjects[0] = sjhzTaskObject;
+        SaveServiceHelper.save(saveDynamicObjects);
+        return true;
+    }
+
+    /**合并数据填报分录*/
+    public static DynamicObject saveMergeSjtbBillEntry(DynamicObject sjhzTaskObject, List<DynamicObject> dynamicObjectList) {
+        DynamicObject [] saveSourceDynamicObjects = new DynamicObject[dynamicObjectList.size()];
+        List<String>  sourceBillNoList = new ArrayList<String>();
+        for (int i = 0;i < dynamicObjectList.size(); i++) {
+            DynamicObject dynamicObject = dynamicObjectList.get(i);
+            dynamicObject.set("nckd_rwzt", "5");
+            saveSourceDynamicObjects[i] = dynamicObject;
+            sourceBillNoList.add(dynamicObject.getString("billno"));
+            // 各级单位信息统计表
+            DynamicObjectCollection nckdGjdwtjbentry = dynamicObject.getDynamicObjectCollection("nckd_gjdwtjbentry");
+            if (nckdGjdwtjbentry.size() > 0) {
+                sjhzTaskObject.set("nckd_zsba", "1");// 展示表a
+                sjhzTaskObject = saveMergeEntry(sjhzTaskObject, nckdGjdwtjbentry, "nckd_gjdwtjbentry");
+            }
+            // 各级单位参股企业信息统计表
+            DynamicObjectCollection nckdGjdwcgqyxxtjb = dynamicObject.getDynamicObjectCollection("nckd_dwcgxxentry");
+            if (nckdGjdwcgqyxxtjb.size() > 0) {
+                sjhzTaskObject.set("nckd_zsbb", "1");// 展示表b
+                sjhzTaskObject = saveMergeEntry(sjhzTaskObject, nckdGjdwcgqyxxtjb, "nckd_dwcgxxentry");
+            }
+            // 各级单位人工成本统计表
+            DynamicObjectCollection nckdDwrgcbentry = dynamicObject.getDynamicObjectCollection("nckd_dwrgcbentry");
+            if (nckdDwrgcbentry.size() > 0) {
+                sjhzTaskObject.set("nckd_zsbc", "1");// 展示表c
+                sjhzTaskObject = saveMergeEntry(sjhzTaskObject, nckdDwrgcbentry, "nckd_dwrgcbentry");
+            }
+            // 各级单位职工收入情况统计表
+            DynamicObjectCollection nckdDwzgsrentry = dynamicObject.getDynamicObjectCollection("nckd_dwzgsrentry");
+            if (nckdDwzgsrentry.size() > 0) {
+                sjhzTaskObject.set("nckd_zsbd", "1");// 展示表d
+                sjhzTaskObject = saveMergeEntry(sjhzTaskObject, nckdDwzgsrentry, "nckd_dwzgsrentry");
+            }
+
+            // 各级单位劳动情况统计表
+            DynamicObjectCollection nckdDwldqkentry = dynamicObject.getDynamicObjectCollection("nckd_dwldqkentry");
+            if (nckdDwldqkentry.size() > 0) {
+                sjhzTaskObject.set("nckd_zsbe", "1");// 展示表e
+                sjhzTaskObject = saveMergeEntry(sjhzTaskObject, nckdDwldqkentry, "nckd_dwldqkentry");
+            }
+
+            // 企业负责人履职待遇、业务支出和社会保险统计表
+            DynamicObjectCollection nckdDwdybxentry = dynamicObject.getDynamicObjectCollection("nckd_dwdybxentry");
+            if (nckdDwdybxentry.size() > 0) {
+                sjhzTaskObject.set("nckd_zsbf", "1");// 展示表f
+                sjhzTaskObject = saveMergeEntry(sjhzTaskObject, nckdDwdybxentry, "nckd_dwdybxentry");
+            }
+        }
+        // 更改源单任务状态
+        SaveServiceHelper.save(saveSourceDynamicObjects);
+        //获取单据体对象并绑定源单单据编号
+        DynamicObjectCollection dynamicObjects = (DynamicObjectCollection) sjhzTaskObject.get("nckd_glsjtbrw");
+        for (String billNo : sourceBillNoList) {
+            //创建一条单据体数据
+            DynamicObject dynamicObjectEntry = dynamicObjects.addNew();
+            dynamicObjectEntry.set("nckd_hzbh", billNo);
+        }
+        return sjhzTaskObject;
+    }
+
+    /**分录表合并*/
+    public static DynamicObject saveMergeEntry(DynamicObject sjhzTaskObject, DynamicObjectCollection nckdGjdwtjbentry, String entryName) {
+        CloneUtils cloneUtils = new CloneUtils(false, true);
+        DynamicObjectCollection sjtbEntryCollection = (DynamicObjectCollection) sjhzTaskObject.get(entryName);
+        for (DynamicObject dynamicObject : nckdGjdwtjbentry) {
+            DynamicObject sjtbEntry = (DynamicObject) cloneUtils.clone(dynamicObject);
+            sjtbEntryCollection.add(sjtbEntry);
+        }
+        return sjhzTaskObject;
+    }
+
+    /**删除汇总表,将所有绑定的单据的任务状态改为已上报*/
+    public static boolean deleteSjcjHzBill(Object pkValue) {
+        List<QFilter> qfilterQuery = new ArrayList<QFilter>();
+        qfilterQuery.add(new QFilter("id", "=", pkValue));
+        QFilter[] filtersQuery = (QFilter[]) qfilterQuery.toArray(new QFilter[qfilterQuery.size()]);
+        DynamicObjectCollection sourceObjects =  QueryServiceHelper.query("nckd_sjtbrw", "id,nckd_glsjtbrw.nckd_hzbh", filtersQuery);
+        String[] billNos = new String[sourceObjects.size()];
+        for (int i = 0; i < sourceObjects.size(); i++) {
+            billNos[i] = sourceObjects.get(i).getString("nckd_glsjtbrw.nckd_hzbh");
+        }
+
+        List<QFilter> qfilterSave = new ArrayList<QFilter>();
+        qfilterSave.add(new QFilter("billno", "in", billNos));
+        QFilter[] filtersSave = (QFilter[]) qfilterSave.toArray(new QFilter[qfilterSave.size()]);
+        DynamicObject[] dynamicObject = BusinessDataServiceHelper.load("nckd_sjtbrw", "id,billno,billstatus,nckd_rwzt", filtersSave);
+        for (int i = 0; i < dynamicObject.length; i++) {
+            dynamicObject[i].set("nckd_rwzt", "2");
+        }
+        SaveServiceHelper.save(dynamicObject);
+        return true;
+    }
+
+}

+ 105 - 0
nckd-hr/src/main/java/nckd/yanye/hr/report/gzbb/SjcjCheckFormListPlugin.java

@@ -0,0 +1,105 @@
+package nckd.yanye.hr.report.gzbb;
+
+import kd.bos.context.RequestContext;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.entity.datamodel.IDataModel;
+import kd.bos.entity.datamodel.ListSelectedRow;
+import kd.bos.entity.datamodel.ListSelectedRowCollection;
+import kd.bos.form.ConfirmTypes;
+import kd.bos.form.MessageBoxOptions;
+import kd.bos.form.events.BeforeDoOperationEventArgs;
+import kd.bos.form.operate.FormOperate;
+import kd.bos.list.plugin.AbstractListPlugin;
+import kd.bos.logging.Log;
+import kd.bos.logging.LogFactory;
+import kd.bos.login.utils.DateUtils;
+import kd.bos.message.api.MessageChannels;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.coderule.CodeRuleServiceHelper;
+import kd.bos.servicehelper.operation.DeleteServiceHelper;
+import kd.bos.servicehelper.operation.SaveServiceHelper;
+import kd.bos.servicehelper.workflow.MessageCenterServiceHelper;
+import kd.bos.workflow.engine.msg.info.MessageInfo;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 数据采集表单列表检查插件
+ */
+public class SjcjCheckFormListPlugin extends AbstractListPlugin {
+    private static final Log logger = LogFactory.getLog(SjcjCheckFormListPlugin.class);
+
+    @Override
+    public void beforeDoOperation(BeforeDoOperationEventArgs args) {
+        FormOperate formOperate = (FormOperate)args.getSource();
+        ListSelectedRowCollection listRows = args.getListSelectedData(); // 获取列表选中行数据
+        int saveResult = 0;
+        String detail = "";
+        if (listRows.size() > 0) {
+            if ("nckd_fblbbtn".equals(formOperate.getOperateKey())) {// 发布
+                for (int i = 0; i < listRows.size(); i++) {
+                    ListSelectedRow listSelectedRow = listRows.get(i);
+                    Object pkValue = listSelectedRow.getPrimaryKeyValue();
+                    // 获取数据填报任务
+                    DynamicObject dynamicObject = BusinessDataServiceHelper.loadSingle(pkValue, "nckd_sjcjrw");
+                    String nckd_sjzt = dynamicObject.getString("nckd_sjzt");// 数据状态
+                    if ("1".equals(nckd_sjzt)) { // 暂存
+                        savePushToNextBill(dynamicObject);
+                        dynamicObject.set("billstatus", "C");// 已审核
+                        dynamicObject.set("nckd_sjzt", "2");// 已下发
+                        SaveServiceHelper.save(new DynamicObject[]{dynamicObject});
+                        saveResult ++;
+                    } else {
+                        detail = detail +"单据"+dynamicObject.getString("billno")+" 数据状态不是暂存 \r\n";
+                    }
+                }
+                this.getView().showConfirm(saveResult + "条数据处理成功," + (listRows.size() - saveResult) + "条数据处理失败", detail, MessageBoxOptions.OK, ConfirmTypes.Save,null,new HashMap<>(),null);
+                this.getView().invokeOperation("refresh");
+            } else if ("nckd_zflbbtn".equals(formOperate.getOperateKey())) {// 作废
+                for (int i = 0; i < listRows.size(); i++) {
+                    ListSelectedRow listSelectedRow = listRows.get(i);
+                    Object pkValue = listSelectedRow.getPrimaryKeyValue();
+                    // 获取数据填报任务
+                    DynamicObject dynamicObject = BusinessDataServiceHelper.loadSingle(pkValue, "nckd_sjcjrw");
+                    String nckd_sjzt = dynamicObject.getString("nckd_sjzt");// 数据状态
+                    if ("2".equals(nckd_sjzt)) { // 已下发
+                        // 作废数据填报单据
+                        PushSjcjDataUtils.deleteSjtbBillData(dynamicObject);
+                        // 作废数据采集单据
+                        PushSjcjDataUtils.deleteSjcjBillData(dynamicObject);
+                        dynamicObject.set("nckd_sjzt", "3");// 已作废
+                        SaveServiceHelper.save(new DynamicObject[]{dynamicObject});
+                        saveResult ++;
+                    } else {
+                        detail = detail +"单据"+dynamicObject.getString("billno")+" 数据状态不是已下发 \r\n";
+                    }
+                }
+                this.getView().showConfirm(saveResult + "条数据处理成功," + (listRows.size() - saveResult) + "条数据处理失败", detail, MessageBoxOptions.OK, ConfirmTypes.Save,null,new HashMap<>(),null);
+                this.getView().invokeOperation("refresh");
+            }
+        }
+        super.beforeDoOperation(args);
+    }
+
+
+
+
+    /**下推生成数据填报任务*/
+    public void savePushToNextBill(DynamicObject dynamicObject) {
+        // 获取任务配置分录
+        DynamicObjectCollection dynamicObjectCollection = dynamicObject.getDynamicObjectCollection("nckd_sjcjrw_entry");
+        for (DynamicObject dynamicObjectEntry: dynamicObjectCollection) {
+            String nckd_sfscrw = dynamicObjectEntry.getString("nckd_sfscrw");// 是否生成下级单位填报任务1是0否
+            DynamicObjectCollection nckd_tztbry = dynamicObjectEntry.getDynamicObjectCollection("nckd_tztbry");// 通知人员
+            List<Long> receivers = nckd_tztbry.stream().map(tztbryObject -> tztbryObject.getLong("fbasedataid.id")).collect(Collectors.toList());
+            if ("1".equals(nckd_sfscrw)) { // 需要生成任务
+                PushSjcjDataUtils.savePushBillData(dynamicObject, dynamicObjectEntry);
+            }
+            PushSjcjDataUtils.savePushBillMsg(dynamicObject, receivers);
+        }
+    }
+
+}

+ 187 - 0
nckd-hr/src/main/java/nckd/yanye/hr/report/gzbb/SjcjCheckFormPlugin.java

@@ -0,0 +1,187 @@
+package nckd.yanye.hr.report.gzbb;
+
+import kd.bos.context.RequestContext;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.dataentity.entity.LocaleString;
+import kd.bos.dataentity.utils.StringUtils;
+import kd.bos.entity.datamodel.IDataModel;
+import kd.bos.entity.datamodel.events.PropertyChangedArgs;
+import kd.bos.entity.operate.result.OperationResult;
+import kd.bos.form.events.AfterDoOperationEventArgs;
+import kd.bos.form.events.BeforeDoOperationEventArgs;
+import kd.bos.form.field.events.BeforeF7SelectEvent;
+import kd.bos.form.field.events.BeforeF7SelectListener;
+import kd.bos.form.operate.FormOperate;
+import kd.bos.form.plugin.AbstractFormPlugin;
+
+import java.io.IOException;
+import java.net.URLDecoder;
+import java.time.LocalDate;
+import java.time.YearMonth;
+import java.time.ZoneId;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import kd.bos.logging.Log;
+import kd.bos.logging.LogFactory;
+import kd.bos.login.utils.DateUtils;
+import kd.bos.message.api.MessageChannels;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.AttachmentServiceHelper;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.QueryServiceHelper;
+import kd.bos.servicehelper.coderule.CodeRuleServiceHelper;
+import kd.bos.servicehelper.operation.SaveServiceHelper;
+import kd.bos.servicehelper.workflow.MessageCenterServiceHelper;
+import kd.bos.web.actions.utils.FilePathUtil;
+import kd.bos.workflow.engine.msg.info.MessageInfo;
+
+/**
+ * 数据采集表单内容检查插件
+ */
+public class SjcjCheckFormPlugin extends AbstractFormPlugin implements BeforeF7SelectListener {
+    private static final Log logger = LogFactory.getLog(SjcjCheckFormPlugin.class);
+
+    @Override
+    public void afterBindData(EventObject e) {
+        super.afterBindData(e);
+    }
+
+    @Override
+    public void afterCopyData(EventObject e) {
+        this.getModel().setValue("nckd_sjzt", "1");// 暂存
+        super.afterCopyData(e);
+    }
+
+    @Override
+    public void afterCreateNewData(EventObject e) {
+        // 获取父页面的数据模型
+        IDataModel parentModel = this.getView().getParentView().getModel();
+        // 获取父页面传递过来的数据参数
+        Map<String,Object> customParams = this.getView().getFormShowParameter().getCustomParams();
+        // 判断是否是从数据填报任务列表过来的
+        if (customParams.get("sjtbrwEntry") != null) {
+            String sjtbrwEntry = customParams.get("sjtbrwEntry").toString();
+            if ("nckd_sjtbrw".equals(sjtbrwEntry)) {// 从数据填报任务列表过来的数据
+                Object sjtbrwId = customParams.get("sjtbrwId");
+                this.initDataModelBySjtbrw(sjtbrwId);
+            }
+        }
+        super.afterCreateNewData(e);
+    }
+
+    /**从数据填报任务列表过来的数据需要初始化字段信息*/
+    public void initDataModelBySjtbrw(Object sjtbrwId) {
+        // 获取到数据填报任务单据
+        DynamicObject sjtbObject = BusinessDataServiceHelper.loadSingle(sjtbrwId, "nckd_sjtbrw");
+        String nckd_rwmc = sjtbObject.getString("nckd_rwmc");// 任务名称
+        Date nckd_tbsjny = sjtbObject.getDate("nckd_tbsjny");// 所属年月
+        String nckd_ydcbm = sjtbObject.getString("nckd_ydcbm");// 源单长编码
+        String nckd_ydbh = sjtbObject.getString("billno");// 源单编号
+        String nckd_ydst = "nckd_sjtbrw";// 源单实体
+        Date nckd_tbjzrq = sjtbObject.getDate("nckd_tbjzrq");// 填报截止时间
+        String nckd_ydcbm_new = nckd_ydcbm + nckd_ydbh + "!";
+
+        this.getModel().setValue("nckd_billname", nckd_rwmc);// 单据名称
+        this.getModel().setValue("nckd_ssny", nckd_tbsjny);// 所属年月
+        this.getModel().setValue("nckd_tbjzsj", nckd_tbjzrq);// 填报截止时间
+        this.getModel().setValue("nckd_sfjtxf", "0");// 是否集团下发
+        this.getModel().setValue("nckd_ydbm", nckd_ydbh);// 源单编码
+        this.getModel().setValue("nckd_ydpk", sjtbrwId);// 源单ID
+        this.getModel().setValue("nckd_ydcbm", nckd_ydcbm_new);// 源单长编码
+        this.getModel().setValue("nckd_ydst", nckd_ydst);
+        this.getModel().setValue("nckd_sjzt", "1");// 数据状态
+    }
+
+    @Override
+    public void registerListener(EventObject e) {
+        super.registerListener(e);
+    }
+
+    @Override
+    public void beforeF7Select(BeforeF7SelectEvent beforeF7SelectEvent) {
+
+    }
+
+    @Override
+    public void beforeDoOperation(BeforeDoOperationEventArgs args) {
+        super.beforeDoOperation(args);
+    }
+
+    @Override
+    public void afterDoOperation(AfterDoOperationEventArgs args) {
+        OperationResult operationResult = args.getOperationResult();
+        // 够获取页面配置的校验规则的执行结果
+        if (operationResult != null && !operationResult.isSuccess()) {
+            return;
+        }
+        FormOperate formOperate = (FormOperate) args.getSource();
+        if ("nckd_fbbtn".equals(formOperate.getOperateKey())) { // 发布按钮
+           if (this.checkDataEntity()) {
+                this.getModel().setValue("billstatus", "C");// 已审核
+                this.getModel().setValue("nckd_sjzt", "2");// 已下发
+                Object[] result = SaveServiceHelper.save(new DynamicObject[]{this.getModel().getDataEntity()});
+                DynamicObject dynamicObject = (DynamicObject) result[0];
+                this.savePushToNextBill(dynamicObject);
+                this.getView().showSuccessNotification("发布成功.");
+                this.getView().updateView();
+            }
+        } else if ("nckd_zfbtn".equals(formOperate.getOperateKey())) { // 作废按钮
+            Long pkValue = (Long) this.getModel().getDataEntity().getPkValue();
+            DynamicObject dynamicObject = BusinessDataServiceHelper.loadSingle(pkValue, "nckd_sjcjrw");
+            // 作废数据填报单据
+            PushSjcjDataUtils.deleteSjtbBillData(dynamicObject);
+            // 作废数据采集单据
+            PushSjcjDataUtils.deleteSjcjBillData(dynamicObject);
+            //this.getModel().setValue("billstatus", "A");// 暂存
+            dynamicObject.set("nckd_sjzt", "1");// 作废
+            SaveServiceHelper.save(new DynamicObject[]{dynamicObject});
+            this.getView().showSuccessNotification("作废成功!");
+            this.getView().updateView();
+        }
+        super.afterDoOperation(args);
+    }
+
+    /**检查任务配置单据体内容*/
+    public boolean checkDataEntity() {
+        return true;
+    }
+
+    @Override
+    public void propertyChanged(PropertyChangedArgs e) {
+        String fieldKey = e.getProperty().getName();
+        if ("nckd_ssny".equals(fieldKey)) {
+            // 获取所在月份的最后一天填入截止日期
+            Date nckd_ssny = (Date) this.getModel().getValue("nckd_ssny");
+            // 1. 将 Date 转换为 LocalDate
+            LocalDate localDate = nckd_ssny.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+            // 2. 计算次月最后一天
+            YearMonth nextMonth = YearMonth.from(localDate).plusMonths(1);
+            LocalDate lastDay = nextMonth.atEndOfMonth();
+            // 3. 将 LocalDate 转换回 Date
+            Date resultDate = Date.from(lastDay.atStartOfDay(ZoneId.systemDefault()).toInstant());
+            this.getModel().setValue("nckd_tbjzsj", resultDate);
+        }
+        super.propertyChanged(e);
+    }
+
+    /**下推生成数据填报任务*/
+    public void savePushToNextBill(DynamicObject dynamicObject) {
+        // 获取分录
+        DynamicObjectCollection dynamicObjectCollection = dynamicObject.getDynamicObjectCollection("nckd_sjcjrw_entry");
+        for (DynamicObject dynamicObjectEntry: dynamicObjectCollection) {
+            String nckd_sfscrw = dynamicObjectEntry.getString("nckd_sfscrw");// 是否生成下级单位填报任务1是0否
+            DynamicObjectCollection nckd_tztbry = dynamicObjectEntry.getDynamicObjectCollection("nckd_tztbry");// 通知人员
+            List<Long> receivers = nckd_tztbry.stream().map(tztbryObject -> tztbryObject.getLong("fbasedataid.id")).collect(Collectors.toList());
+            if ("1".equals(nckd_sfscrw)) { // 需要生成任务
+                PushSjcjDataUtils.savePushBillData(dynamicObject, dynamicObjectEntry);
+            }
+            PushSjcjDataUtils.savePushBillMsg(dynamicObject, receivers);
+        }
+
+    }
+
+
+
+}

+ 33 - 0
nckd-hr/src/main/java/nckd/yanye/hr/report/gzbb/SjtbBackWorkflowPlugin.java

@@ -0,0 +1,33 @@
+package nckd.yanye.hr.report.gzbb;
+
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.operation.SaveServiceHelper;
+import kd.bos.workflow.api.AgentExecution;
+import kd.bos.workflow.api.constants.WFTaskResultEnum;
+import kd.bos.workflow.engine.extitf.IWorkflowPlugin;
+import kd.sdk.plugin.Plugin;
+
+public class SjtbBackWorkflowPlugin implements Plugin, IWorkflowPlugin {
+
+    @Override
+    public void notify(AgentExecution execution) {
+        String businessKey = execution.getBusinessKey();
+        String entityNumber = execution.getEntityNumber();
+        if ("nckd_sjtbrw".equals(entityNumber)) {
+            Long pkValue = Long.parseLong(businessKey);
+            DynamicObject dynamicObject = BusinessDataServiceHelper.loadSingle(pkValue, "nckd_sjtbrw");
+            String nckd_rwzt = dynamicObject.getString("nckd_rwzt");
+            if (!"3".equals(nckd_rwzt)) {// 没有作废
+                dynamicObject.set("nckd_rwzt", "4");// 已打回
+                SaveServiceHelper.save(new DynamicObject[]{dynamicObject});
+            }
+        }
+        IWorkflowPlugin.super.notify(execution);
+    }
+
+    @Override
+    public void notifyByWithdraw(AgentExecution execution) {
+        IWorkflowPlugin.super.notifyByWithdraw(execution);
+    }
+}

+ 192 - 0
nckd-hr/src/main/java/nckd/yanye/hr/report/gzbb/SjtbCheckFormListPlugin.java

@@ -0,0 +1,192 @@
+package nckd.yanye.hr.report.gzbb;
+
+import kd.bos.bill.BillShowParameter;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.utils.StringUtils;
+import kd.bos.entity.datamodel.ListSelectedRow;
+import kd.bos.entity.datamodel.ListSelectedRowCollection;
+import kd.bos.entity.datamodel.events.PackageDataEvent;
+import kd.bos.form.CloseCallBack;
+import kd.bos.form.ShowType;
+import kd.bos.form.StyleCss;
+import kd.bos.form.events.BeforeCreateListDataProviderArgs;
+import kd.bos.form.events.BeforeDoOperationEventArgs;
+import kd.bos.form.events.ClosedCallBackEvent;
+import kd.bos.form.operate.FormOperate;
+import kd.bos.form.operatecol.OperationColItem;
+import kd.bos.list.column.ListOperationColumnDesc;
+import kd.bos.list.events.BillClosedCallBackEvent;
+import kd.bos.list.plugin.AbstractListPlugin;
+import kd.bos.logging.Log;
+import kd.bos.logging.LogFactory;
+import kd.bos.login.utils.DateUtils;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.QueryServiceHelper;
+
+import java.util.*;
+
+/**数据填报列表插件*/
+public class SjtbCheckFormListPlugin extends AbstractListPlugin {
+    private static final Log logger = LogFactory.getLog(SjtbCheckFormListPlugin.class);
+
+    @Override
+    public void beforeDoOperation(BeforeDoOperationEventArgs args) {
+        FormOperate operate = (FormOperate)args.getSource();
+        if ("nckd_xfrw".equals(operate.getOperateKey())) {// 下发任务
+            ListSelectedRowCollection listRows = args.getListSelectedData();
+            if (listRows.size() != 1) {
+                this.getView().showErrorNotification("请选择一行数据进行操作");
+                args.setCancel(true);
+                return;
+            } else {
+                ListSelectedRow listSelectedRow = listRows.get(0);
+                Object pkValue = listSelectedRow.getPrimaryKeyValue();
+                DynamicObject dynamicObject = BusinessDataServiceHelper.loadSingle(pkValue, "nckd_sjtbrw");
+                String billstatus = dynamicObject.getString("billstatus");// 单据状态
+                String nckd_rwzt = dynamicObject.getString("nckd_rwzt");// 任务状态
+                // 单据暂存中且任务状态为待上报或已打回
+                if ("A".equals(billstatus) && ("1".equals(nckd_rwzt) || "4".equals(nckd_rwzt))) {
+                    Map<String, Object> customParamMap = new HashMap<>();
+                    customParamMap.put("sjtbrwId", pkValue);// 数据填报任务id
+                    customParamMap.put("sjtbrwEntry", "nckd_sjtbrw");// 数据填报任务实体
+                    //创建弹出单据页面对象,并赋值
+                    BillShowParameter billShowParameter = new BillShowParameter();
+                    //设置弹出子单据页面的标识
+                    billShowParameter.setFormId("nckd_sjcjrw");
+                    //设置弹出子单据页面的标题
+                    billShowParameter.setCaption("数据采集任务");
+                    //设置弹出子单据页面的打开方式
+                    billShowParameter.getOpenStyle().setShowType(ShowType.MainNewTabPage);
+                    //设置子页面关闭回调对象,回调本插件,标识为常量KEY_LEAVE_DAYS对应的值
+                    billShowParameter.setCloseCallBack(new CloseCallBack(this, "KEY_NCKD_XFRW_BTN"));
+                    // 给子单据传值
+                    billShowParameter.setCustomParams(customParamMap);
+                    //弹窗子页面和父页面绑定
+                    this.getView().showForm(billShowParameter);
+                } else {
+                    this.getView().showErrorNotification("请选择暂存中的数据进行下发");
+                    args.setCancel(true);
+                    this.getView().invokeOperation("refresh");
+                    return;
+                }
+            }
+        } else if ("nckd_hzbtn".equals(operate.getOperateKey())) { // 汇总
+            ListSelectedRowCollection listRows = args.getListSelectedData();
+            List<DynamicObject> copyList = new ArrayList<DynamicObject>();
+            Date firstDate = null;
+            int flag = 0;
+            for (int i = 0; i < listRows.size(); i++) {
+                Object pkValue = listRows.get(i).getPrimaryKeyValue();
+                DynamicObject dynamicObject = BusinessDataServiceHelper.loadSingle(pkValue, "nckd_sjtbrw");
+                String billstatus = dynamicObject.getString("billstatus");// 单据状态
+                String nckd_rwzt = dynamicObject.getString("nckd_rwzt");// 任务状态
+                Date nckd_tbsjny = dynamicObject.getDate("nckd_tbsjny");// 数据填报年月
+                if (firstDate == null) {
+                    firstDate = nckd_tbsjny;
+                } else {
+                    String firstDateStr = DateUtils.formatDate(firstDate, "yyyy-MM");
+                    String nckd_tbsjnyStr = DateUtils.formatDate(nckd_tbsjny, "yyyy-MM");
+                    if (!firstDateStr.equals(nckd_tbsjnyStr)) {
+                        flag = 1;
+                        break;
+                    }
+                }
+                if (!"C".equals(billstatus) || !"2".equals(nckd_rwzt)) { // 非已审核或非已上报
+                    flag = 2;
+                    break;
+                }
+                copyList.add(dynamicObject);
+            }
+            if (flag != 0) {
+                if (flag == 1) {
+                    this.getView().showErrorNotification("请选择同一年月的数据进行操作");
+                    return;
+                } else {
+                    this.getView().showErrorNotification("请选择已审核且已上报的数据进行操作");
+                    return;
+                }
+            } else {
+               boolean saveResult = PushSjcjDataUtils.saveMergeSjtbBill(copyList);
+               if (saveResult) {
+                   this.getView().showSuccessNotification("汇总成功");
+                   this.getView().invokeOperation("refresh");
+               } else {
+                   this.getView().showErrorNotification("汇总失败");
+               }
+            }
+        }
+        super.beforeDoOperation(args);
+    }
+
+    @Override
+    public void beforeCreateListDataProvider(BeforeCreateListDataProviderArgs args) {
+        super.beforeCreateListDataProvider(args);
+    }
+
+    @Override
+    public void packageData(PackageDataEvent e) {
+        super.packageData(e);
+        // 判断是操作列
+        if (e.getSource() instanceof ListOperationColumnDesc) {
+            DynamicObject rowData = e.getRowData();
+            String columnKey = ((ListOperationColumnDesc) e.getSource()).getKey();
+            // 获取操作列的所有操作项
+            List<OperationColItem> operationColItems = (List<OperationColItem>) e.getFormatValue();
+            if (StringUtils.equals("nckd_sjtbczl", columnKey)) {
+                String billstatus = rowData.getString("billstatus");// 单据状态
+                String nckd_rwzt = rowData.getString("nckd_rwzt");// 任务状态
+                String nckd_bblx = rowData.getString("nckd_bblx");// 报表类型
+                // 根据单据状态,设置操作列的按钮灰显
+                for (OperationColItem item : operationColItems) {
+                    if ("modify".equals(item.getOperationKey())) {// 填报按钮
+                        // 单据暂存中且任务状态为待上报或已打回且是填报表
+                        if ("A".equals(billstatus) && ("1".equals(nckd_rwzt) || "4".equals(nckd_rwzt)) && "1".equals(nckd_bblx)) {
+                            item.setVisible(true);
+                            //设置锁定解锁
+                            //item.setLocked(false);
+                        } else {
+                           item.setVisible(false);
+                        }
+                    } else if ("nckd_xfrw".equals(item.getOperationKey())) {// 下发任务按钮
+                        // 单据暂存中且任务状态为待上报或已打回且是填报表
+                        if ("A".equals(billstatus) && ("1".equals(nckd_rwzt) || "4".equals(nckd_rwzt)) && "1".equals(nckd_bblx)) {
+                            item.setVisible(true);
+                        } else {
+                            item.setVisible(false);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 子界面关闭时,触发父界面的closedCallBack事件;
+     * 父界面的插件,可以在此事件,接收子界面返回的数据。
+     * 特别说明:
+     * 需要在显示子界面时,调用FormShowParameter参数的setCloseCallBack方法,设置回调属性,才会在子界面关闭时触发此事件:
+     */
+    @Override
+    public void closedCallBack(ClosedCallBackEvent closedCallBackEvent) {
+        super.closedCallBack(closedCallBackEvent);
+        //返回数据不为空并且标识为常量KEY_LEAVE_DAYS对应的值,代表是我们所监控的子页面关闭事件
+        if (StringUtils.equals("KEY_NCKD_XFRW_BTN", closedCallBackEvent.getActionId())) {
+            this.getView().invokeOperation("refresh");
+        }
+    }
+
+    @Override
+    public void billClosedCallBack(BillClosedCallBackEvent e) {
+        Object pkId = e.getPkId();
+        boolean isDel = QueryServiceHelper.exists("nckd_sjtbrw", pkId);
+        if (!isDel) {
+            this.getView().showSuccessNotification("删除成功");
+        }
+        this.getView().invokeOperation("refresh");
+        super.billClosedCallBack(e);
+    }
+
+
+
+}

+ 109 - 0
nckd-hr/src/main/java/nckd/yanye/hr/report/gzbb/SjtbCheckFormPlugin.java

@@ -0,0 +1,109 @@
+package nckd.yanye.hr.report.gzbb;
+
+import kd.bos.context.RequestContext;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.entity.operate.result.OperationResult;
+import kd.bos.form.ConfirmCallBackListener;
+import kd.bos.form.MessageBoxOptions;
+import kd.bos.form.MessageBoxResult;
+import kd.bos.form.events.AfterDoOperationEventArgs;
+import kd.bos.form.events.BeforeDoOperationEventArgs;
+import kd.bos.form.events.MessageBoxClosedEvent;
+import kd.bos.form.operate.FormOperate;
+import kd.bos.form.plugin.AbstractFormPlugin;
+import kd.bos.logging.Log;
+import kd.bos.logging.LogFactory;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.QueryServiceHelper;
+import kd.bos.servicehelper.operation.DeleteServiceHelper;
+import kd.bos.servicehelper.operation.SaveServiceHelper;
+
+import java.util.ArrayList;
+import java.util.EventObject;
+import java.util.List;
+
+/**数据填报单据插件*/
+public class SjtbCheckFormPlugin extends AbstractFormPlugin {
+    private static final Log logger = LogFactory.getLog(SjtbCheckFormPlugin.class);
+
+    @Override
+    public void beforeDoOperation(BeforeDoOperationEventArgs args) {
+        super.beforeDoOperation(args);
+    }
+
+    @Override
+    public void afterDoOperation(AfterDoOperationEventArgs args) {
+        OperationResult operationResult = args.getOperationResult();
+        // 够获取页面配置的校验规则的执行结果
+        if (operationResult != null && !operationResult.isSuccess()) {
+            return;
+        }
+        FormOperate formOperate = (FormOperate) args.getSource();
+        if ("nckd_zjspbtn".equals(formOperate.getOperateKey())) { // 直接审批
+            this.getModel().setValue("billstatus", "C");// 已审核
+            this.getModel().setValue("nckd_rwzt", "1");// 待上报
+            Object[] result = SaveServiceHelper.save(new DynamicObject[]{this.getModel().getDataEntity()});
+            DynamicObject dynamicObject = (DynamicObject) result[0];
+            SaveServiceHelper.save(new DynamicObject[]{dynamicObject});
+            this.getView().showSuccessNotification("审核成功");
+            this.getView().invokeOperation("refresh");
+        } else if ("nckd_fshbtn".equals(formOperate.getOperateKey())) { // 反审核
+            Long pkValue = (Long) this.getModel().getDataEntity().getPkValue();
+            DynamicObject dynamicObject = BusinessDataServiceHelper.loadSingle(pkValue, "nckd_sjtbrw");
+            dynamicObject.set("billstatus", "A");// 暂存
+            dynamicObject.set("nckd_rwzt", "1");// 待上报
+            SaveServiceHelper.save(new DynamicObject[]{dynamicObject});
+            this.getView().showSuccessNotification("反审核成功");
+            this.getView().invokeOperation("refresh");
+        } else if ("nckd_sbbtn".equals(formOperate.getOperateKey())) { // 上报
+            //上报操作前让用户选择是否继续上报操作,在用户点击确认框上的按钮后,系统会调用confirmCallBack方法
+            ConfirmCallBackListener confirmCallBackListener = new ConfirmCallBackListener("querenshangbaoOp", this);
+            //设置页面确认框,参数为:标题,选项框类型,回调监听
+            this.getView().showConfirm("请确认是否上报吗?", MessageBoxOptions.YesNo, confirmCallBackListener);
+        } else if ("nckd_scbtn".equals(formOperate.getOperateKey())) { // 删除
+            //上报操作前让用户选择是否继续上报操作,在用户点击确认框上的按钮后,系统会调用confirmCallBack方法
+            ConfirmCallBackListener confirmCallBackListener = new ConfirmCallBackListener("querenShanChuOp", this);
+            //设置页面确认框,参数为:标题,选项框类型,回调监听
+            this.getView().showConfirm("请确认是否删除吗?", MessageBoxOptions.YesNo, confirmCallBackListener);
+        }
+        super.afterDoOperation(args);
+    }
+
+    @Override
+    public void afterCreateNewData(EventObject e) {
+        super.afterCreateNewData(e);
+    }
+
+    @Override
+    public void confirmCallBack(MessageBoxClosedEvent messageBoxClosedEvent) {
+        // TODO Auto-generated method stub
+        super.confirmCallBack(messageBoxClosedEvent);
+        //判断回调参数id
+        if("querenshangbaoOp".equals(messageBoxClosedEvent.getCallBackId())){
+            if (MessageBoxResult.Yes.equals(messageBoxClosedEvent.getResult())) {
+                Long pkValue = (Long) this.getModel().getDataEntity().getPkValue();
+                DynamicObject dynamicObject = BusinessDataServiceHelper.loadSingle(pkValue, "nckd_sjtbrw");
+                dynamicObject.set("nckd_rwzt", "2");// 已上报
+                SaveServiceHelper.save(new DynamicObject[]{dynamicObject});
+                this.getView().showSuccessNotification("上报成功");
+                this.getView().invokeOperation("refresh");
+            }
+        } else if("querenShanChuOp".equals(messageBoxClosedEvent.getCallBackId())){
+            if (MessageBoxResult.Yes.equals(messageBoxClosedEvent.getResult())) {
+                Long pkValue = (Long) this.getModel().getDataEntity().getPkValue();
+                boolean isDelete = PushSjcjDataUtils.deleteSjcjHzBill(pkValue);
+                if (isDelete) {
+                    //this.getView().showSuccessNotification("删除成功");
+                    List<QFilter> qfilterQuery = new ArrayList<QFilter>();
+                    qfilterQuery.add(new QFilter("id", "=", pkValue));
+                    QFilter[] filtersQuery = (QFilter[]) qfilterQuery.toArray(new QFilter[qfilterQuery.size()]);
+                    DeleteServiceHelper.delete("nckd_sjtbrw", filtersQuery);
+                    this.getView().close();
+                }
+            }
+        }
+    }
+
+}

+ 33 - 0
nckd-hr/src/main/java/nckd/yanye/hr/report/gzbb/SjtbSubmitWorkflowPlugin.java

@@ -0,0 +1,33 @@
+package nckd.yanye.hr.report.gzbb;
+
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.operation.SaveServiceHelper;
+import kd.bos.workflow.api.AgentExecution;
+import kd.bos.workflow.engine.extitf.IWorkflowPlugin;
+import kd.sdk.plugin.Plugin;
+
+/**数据填报单据提交进入流程*/
+public class SjtbSubmitWorkflowPlugin implements Plugin, IWorkflowPlugin {
+
+    @Override
+    public void notify(AgentExecution execution) {
+        String businessKey = execution.getBusinessKey();
+        String entityNumber = execution.getEntityNumber();
+        if ("nckd_sjtbrw".equals(entityNumber)) {
+            Long pkValue = Long.parseLong(businessKey);
+            DynamicObject dynamicObject = BusinessDataServiceHelper.loadSingle(pkValue, "nckd_sjtbrw");
+            String nckd_rwzt = dynamicObject.getString("nckd_rwzt");
+            if (!"3".equals(nckd_rwzt)) {// 没有作废
+                dynamicObject.set("nckd_rwzt", "1");// 待上报
+                SaveServiceHelper.save(new DynamicObject[]{dynamicObject});
+            }
+        }
+        IWorkflowPlugin.super.notify(execution);
+    }
+
+    @Override
+    public void notifyByWithdraw(AgentExecution execution) {
+        IWorkflowPlugin.super.notifyByWithdraw(execution);
+    }
+}

+ 14 - 0
nckd-pur/build.gradle

@@ -0,0 +1,14 @@
+/*
+ * This is a kingdee cosmic template project that is automatically generated by the Kingdee cosmic development assistant plugin. 
+ * If there are any issues during the use process, you can provide feedback to the kingdee developer community website.
+ * Website: https://developer.kingdee.com/developer?productLineId=29
+ * Author: liebin.zheng
+ * Generate Date: 2025-11-24 15:33:29
+ */
+
+dependencies {
+	api project(':nckd-base-common')
+	api project(':nckd-base-helper')
+} 
+
+

+ 105 - 0
nckd-pur/src/main/java/nckd/pur/scp/business/SendMsgToWechatOA.java

@@ -0,0 +1,105 @@
+package nckd.pur.scp.business;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.exception.ErrorCode;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.workflow.engine.WfConfigurationUtil;
+import kd.bos.workflow.engine.msg.AbstractMessageServiceHandler;
+import kd.bos.workflow.engine.msg.ctx.MessageContext;
+import kd.bos.workflow.engine.msg.info.ToDoInfo;
+import kd.bos.workflow.exception.WFMessageServiceException;
+import org.apache.log4j.Logger;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 动态表单插件
+ */
+public class SendMsgToWechatOA extends AbstractMessageServiceHandler {
+
+    Logger logger = Logger.getLogger(SendMsgToWechatOA.class.getName());
+    private static final String SEND_SUBSCRIBE_MSG_URL = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=ACCESS_TOKEN";
+    private static final String CHANNEL_TYPE = "wxgzh";
+    @Override
+    public void createToDo(MessageContext msgcontext, ToDoInfo todoinfo) {
+        if (WfConfigurationUtil.isEnabled(CHANNEL_TYPE)) {
+            logger.info("---createToDo---入参参数:msgcontext = " + msgcontext.toString());
+            logger.info("---createToDo---入参参数:todoinfo = " + todoinfo.toString());
+            try {
+                String content = todoinfo.getContent();// 标题内容
+                // 根据id查询手机号码,并去重
+                List<Long> userIds = todoinfo.getUserIds();// 待办任务处理人主键id
+                Set<String> userPhone = new HashSet<>();
+                DynamicObject[] load = BusinessDataServiceHelper.load("bos_user", "id,phone",
+                        new QFilter("id", QCP.in, userIds).toArray());
+                for (DynamicObject user : load) {
+                    userPhone.add(user.getString("phone"));
+                }
+                // 构建发送短信请求信息
+//                String requestParams = SendMsgUtils.createParams(userPhone, content);
+                // 请求短信发送接口
+//                SendMsgUtils.InsertDownSms(requestParams);
+            } catch (Exception e) {
+                logger.error("xxxxx发送待办短信插件创建createToDo失败" + e.getMessage() + "//" + e.getCause() + "//"
+                        + e.getLocalizedMessage());
+                throw new WFMessageServiceException(e, new ErrorCode("bos.wf.msg.lhsmsCreatToDoError", "\"xxxxx发送待办短信"),
+                        e.getMessage());
+            }
+        }
+    }
+
+    public void sendTodoReminder(String openid, String templateId, String todoTitle, String dueTime) {
+        // 1. 获取 access_token(建议缓存,有效期2小时)
+        String accessToken = getAccessToken("","");
+
+        // 2. 构建消息体
+        JSONObject msg = new JSONObject();
+        msg.put("touser", openid);
+        msg.put("template_id", templateId);
+
+        JSONObject data = new JSONObject();
+        // 模板中的字段名需匹配
+        data.put("thing1", todoTitle);
+        data.put("time2", dueTime);
+        msg.put("data", data);
+
+        // 3. 发送请求
+        RestTemplate restTemplate = new RestTemplate();
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_JSON);
+        HttpEntity<String> entity = new HttpEntity<>(msg.toJSONString(), headers);
+
+        String url = SEND_SUBSCRIBE_MSG_URL.replace("ACCESS_TOKEN", accessToken);
+        String resp = restTemplate.postForObject(url, entity, String.class);
+        System.out.println("Send result: " + resp);
+    }
+
+    @Override
+    public void dealToDo(MessageContext messageContext, ToDoInfo toDoInfo) {
+
+    }
+
+    @Override
+    public void deleteToDo(MessageContext messageContext, ToDoInfo toDoInfo) {
+
+    }
+
+    private String getAccessToken(String appId,String secret) {
+        String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" +
+                appId + "&secret=" + secret;
+        RestTemplate restTemplate = new RestTemplate();
+        String resp = restTemplate.getForObject(url, String.class);
+        JSONObject json = JSON.parseObject(resp);
+        return json.getString("access_token");
+    }
+}

+ 22 - 0
nckd-pur/src/main/java/nckd/pur/scp/common/AppflgConstant.java

@@ -0,0 +1,22 @@
+/**
+ * This is a kingdee cosmic template project that is automatically generated by the Kingdee cosmic development assistant plugin. 
+ * If there are any issues during the use process, you can provide feedback to the kingdee developer community website.
+ * Website: https://developer.kingdee.com/developer?productLineId=29
+ * Author: liebin.zheng
+ * Generate Date: 2025-11-24 15:33:29
+ */
+package nckd.pur.scp.common;
+
+/**
+ * pur云scp应用-通用常量类<br>
+ * 代码中不能存在硬编码敏感信息,如账号、密码、http外链、ftp外链、邮箱等。<br>
+ * 标识或缓存的常量,需以"KEY_"、"FID_"、"ENTRY_"或"SUBENTRY_"作为变量的前缀。<br>
+ *
+ * @author nckd
+ * @date 2025-11-24 15:33:29
+ */
+public class AppflgConstant {
+	
+	public static final String KEY_APP_NAME = "pur-scp";
+
+}

+ 736 - 0
nckd-pur/src/main/java/nckd/pur/scp/common/DateUtil.java

@@ -0,0 +1,736 @@
+package nckd.pur.scp.common;
+
+
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.utils.ObjectUtils;
+import kd.bos.entity.EntityMetadataCache;
+import kd.bos.exception.ErrorCode;
+import kd.bos.exception.KDBizException;
+import kd.bos.exception.KDException;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.util.StringUtils;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.*;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalAdjusters;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Set;
+
+/**
+ * 日期工具类
+ *
+ * @author xyc
+ * @date 2021/8/4
+ */
+public final class DateUtil {
+
+	public static final String DATE_FORMAT_YYYY_MM_DD = "yyyy-MM-dd";
+
+	public static final String DATE_TIME_FORMAT_YYYY_MM_DD_HH_MI_SS = "yyyy-MM-dd HH:mm:ss";
+
+	public static final String DATE_TIME_FORMAT_YYYY_MM_DD_HH_MI_SS_SSS = "yyyyMMddHHmmssSSS";
+
+	public static final String DATE_FORMAT_YYYYMM = "yyyyMM";
+
+	public static final String DATE_FORMAT_YYYYMMDD = "yyyyMMdd";
+
+	public static final ZoneId UTC_PLUS_8 = ZoneId.systemDefault();
+
+	private DateUtil() {
+	}
+
+	public static boolean isCurrentDateInRange(Date startDate, Date endDate) {
+		Date currentDate = new Date();
+		return isDateInRange(currentDate, startDate, endDate);
+	}
+
+	public static boolean isDateInRange(Date targetDate, Date startDate, Date endDate) {
+		return !targetDate.before(startDate) && !targetDate.after(endDate);
+	}
+
+	/**
+	 * 日期转字符串
+	 *
+	 * @param date    指定日期
+	 * @param pattern 格式
+	 * @return 返回String格式 ( yyyy-MM-dd HH:mm:ss )
+	 */
+	public static String date2str(Date date, String pattern) {
+		if (null == date) {
+			return null;
+		}
+		SimpleDateFormat format = null;
+		if (StringUtils.isNotEmpty(pattern)) {
+			format = new SimpleDateFormat(pattern);
+		} else {
+			format = new SimpleDateFormat(DATE_TIME_FORMAT_YYYY_MM_DD_HH_MI_SS);
+		}
+		return format.format(date);
+	}
+
+	/**
+	 * 获取当前时间字符串
+	 *
+	 * @param pattern 格式
+	 * @return String
+	 */
+	public static String getCurrentDateTimeStr(String pattern) {
+		return date2str(new Date(), pattern);
+	}
+
+	/**
+	 * 字符串转日期
+	 *
+	 * @param str     指定日期
+	 * @param pattern 格式
+	 * @return date
+	 */
+	public static Date string2date(String str, String pattern) {
+		SimpleDateFormat format = null;
+		if (StringUtils.isNotEmpty(pattern)) {
+			format = new SimpleDateFormat(pattern);
+		} else {
+			format = new SimpleDateFormat(DATE_TIME_FORMAT_YYYY_MM_DD_HH_MI_SS);
+		}
+		Date date = null;
+		if (!StringUtils.isEmpty(str)) {
+			try {
+				date = format.parse(str);
+			} catch (ParseException e) {
+				throw new KDException(new ErrorCode("time convert error", "日期%s转换异常"), str);
+			}
+		}
+		return date;
+	}
+
+
+	/**
+	 * 将指定日期的时分秒格式那天的 00:00:00 的时间
+	 *
+	 * @param date 指定日期
+	 * @return date
+	 */
+	public static Date getDateStartTime(Date date) {
+		if (null == date) {
+			return null;
+		}
+		LocalDate localDate = date.toInstant().atZone(UTC_PLUS_8).toLocalDate();
+		LocalDateTime localDateTime = localDate.atStartOfDay();
+		return localDateTime2date(localDateTime);
+	}
+
+	/**
+	 * 将指定日期的时分秒格式那天的 23:59:59 的时间
+	 *
+	 * @param date 指定日期
+	 * @return date
+	 */
+	public static Date getDateEndTime(Date date) {
+		LocalDate localDate = date.toInstant().atZone(UTC_PLUS_8).toLocalDate();
+		LocalDateTime localDateTime = localDate.atTime(23, 59, 59);
+		return localDateTime2date(localDateTime);
+	}
+
+	/**
+	 * 获取指定日期当月最后一天的时间
+	 *
+	 * @param date 指定日期
+	 * @return 最后一天的时间 (时分秒:23:59:59)
+	 */
+	public static Date getTimeEndOfMonth(Date date) {
+		LocalDate localDate = date.toInstant().atZone(UTC_PLUS_8).toLocalDate();
+		LocalDateTime localDateTime = localDate.atTime(23, 59, 59).with(TemporalAdjusters.lastDayOfMonth());
+		return localDateTime2date(localDateTime);
+	}
+
+	/**
+	 * 月结束日期
+	 *
+	 * @param date 指定日期
+	 * @return date
+	 */
+	public static Date getDateEndOfMonth(Date date) {
+		LocalDate localDate = date.toInstant().atZone(UTC_PLUS_8).toLocalDate();
+		localDate = localDate.withDayOfMonth(localDate.lengthOfMonth());
+		return Date.from(localDate.atStartOfDay().atZone(UTC_PLUS_8).toInstant());
+	}
+
+	/**
+	 * 获取指定日期当月第一天的时间
+	 *
+	 * @param date 指定日期
+	 * @return 第一天的时间 (时分秒:00:00:00)
+	 */
+	public static Date getDateStartOfMonth(Date date) {
+		LocalDate localDate = date.toInstant().atZone(UTC_PLUS_8).toLocalDate();
+		LocalDateTime localDateTime = localDate.atStartOfDay().with(TemporalAdjusters.firstDayOfMonth());
+		return localDateTime2date(localDateTime);
+	}
+
+	/**
+	 * 周开始日期
+	 *
+	 * @param date        指定日期
+	 * @param isChinaWeek 是否按中国的习惯(一个星期的第一天是星期一)
+	 * @return Date
+	 */
+	public static Date getDateStartOfWeek(Date date, boolean isChinaWeek) {
+		LocalDate localDate = date.toInstant().atZone(UTC_PLUS_8).toLocalDate();
+		int days = localDate.getDayOfWeek().getValue() % 7;
+		if (isChinaWeek) {
+			days = days - 1;
+		}
+		localDate = localDate.minusDays(days);
+		return Date.from(localDate.atStartOfDay().atZone(UTC_PLUS_8).toInstant());
+	}
+
+	/**
+	 * 周结束日期
+	 *
+	 * @param date        指定日期
+	 * @param isChinaWeek 是否按中国的习惯(一个星期的第一天是星期一)
+	 * @return Date
+	 */
+	public static Date getDateEndOfWeek(Date date, boolean isChinaWeek) {
+		LocalDate localDate = date.toInstant().atZone(UTC_PLUS_8).toLocalDate();
+		int days = localDate.getDayOfWeek().getValue() % 7;
+		if (isChinaWeek) {
+			days = days - 1;
+		}
+		localDate = localDate.plusDays(7 - 1 - days);
+		return Date.from(localDate.atStartOfDay().atZone(UTC_PLUS_8).toInstant());
+	}
+
+	/**
+	 * 获取指定日期当年第一天的时间
+	 *
+	 * @param date 指定日期
+	 * @return date
+	 */
+	public static Date getDateStartOfYear(Date date) {
+		LocalDate localDate = date.toInstant().atZone(UTC_PLUS_8).toLocalDate();
+		localDate = localDate.withMonth(1).withDayOfMonth(1);
+		return Date.from(localDate.atStartOfDay().atZone(UTC_PLUS_8).toInstant());
+	}
+
+	/**
+	 * 获取指定日期当年最后一天的时间
+	 *
+	 * @param date 指定日期
+	 * @return date
+	 */
+	public static Date getDateEndOfYear(Date date) {
+		LocalDate localDate = date.toInstant().atZone(UTC_PLUS_8).toLocalDate();
+		localDate = localDate.withMonth(12);
+		localDate = localDate.withDayOfMonth(localDate.lengthOfMonth());
+		return Date.from(localDate.atStartOfDay().atZone(UTC_PLUS_8).toInstant());
+	}
+
+	/**
+	 * 获取年龄
+	 *
+	 * @param birthDate
+	 * @param currentDate
+	 * @return
+	 */
+	public static int getAge(Date birthDate, Date currentDate) {
+		if (ObjectUtils.isEmpty(birthDate)) {
+			throw new KDBizException("生日不能为空");
+		}
+		Date date = currentDate;
+		if (ObjectUtils.isEmpty(date)) {
+			date = new Date();
+		}
+		LocalDate birthLocalDate = date2localDate(birthDate);
+		LocalDate currentLocalDate = date2localDate(date);
+		return (int) birthLocalDate.until(currentLocalDate, ChronoUnit.DAYS);
+	}
+
+	/**
+	 * 获取俩个日期间天数,如果入参为空时返回0
+	 *
+	 * @param firstDate 开始时间
+	 * @param secDate   结束时间
+	 * @return 返回俩个日期间天数的绝对值
+	 */
+	public static int daysBetweenIfBlankReturnZero(Date firstDate, Date secDate) {
+		if (firstDate == null || secDate == null) {
+			return 0;
+		}
+		return daysBetween(firstDate, secDate);
+	}
+
+	/**
+	 * 获取俩个日期间天数 ( 正整数 )
+	 *
+	 * @param firstDate 开始时间
+	 * @param secDate   结束时间
+	 * @return 返回俩个日期间天数的绝对值
+	 */
+	public static int daysBetween(Date firstDate, Date secDate) {
+		LocalDate localDate1 = date2localDate(firstDate);
+		LocalDate localDate2 = date2localDate(secDate);
+		return localDate2.isAfter(localDate1) ? (int) localDate1.until(localDate2, ChronoUnit.DAYS) : (int) localDate2.until(localDate1, ChronoUnit.DAYS);
+	}
+
+	/**
+	 * 获得指定时间加减月数后的日期
+	 *
+	 * @param date  指定日期
+	 * @param month 月数,可正可负
+	 * @return 计算后的日期
+	 */
+	public static Date addMonth(Date date, int month) {
+		LocalDateTime localDateTime = date2localDateTime(date);
+		return localDateTime2date(localDateTime.plusMonths(month));
+	}
+
+	/**
+	 * 获得指定时间加减星期后的日期
+	 *
+	 * @param date 指定日期
+	 * @param week 星期,可正可负
+	 * @return 计算后的日期
+	 */
+	public static Date addWeek(Date date, int week) {
+		LocalDateTime localDateTime = date2localDateTime(date);
+		return localDateTime2date(localDateTime.plusWeeks(week));
+	}
+
+	/**
+	 * 获得指定时间第二天的日期
+	 *
+	 * @param date 指定时间
+	 * @return 计算后的日期
+	 */
+	public static Date getNextDay(Date date) {
+		LocalDateTime localDateTime = date2localDateTime(date);
+		return localDateTime2date(localDateTime.plusDays(1L));
+	}
+
+	/**
+	 * 获得指定时间加减天数后的日期
+	 *
+	 * @param date 指定日期
+	 * @param days 天数,可正可负
+	 * @return 计算后的日期
+	 */
+	public static Date addDay(Date date, int days) {
+		LocalDateTime localDateTime = date2localDateTime(date);
+		return localDateTime2date(localDateTime.plusDays(days));
+	}
+
+	/**
+	 * 获得指定时间加减小时后的时间
+	 *
+	 * @param date  指定日期
+	 * @param hours 小时数,可正可负
+	 * @return 计算后的日期
+	 */
+	public static Date addHour(Date date, int hours) {
+		LocalDateTime localDateTime = date2localDateTime(date);
+		return localDateTime2date(localDateTime.plusHours(hours));
+	}
+
+	/**
+	 * 获得指定时间加减小时后的时间
+	 * @param date  指定日期
+	 * @param seconds 秒,可正可负
+	 * @return 计算后的日期
+	 */
+	public static Date addSecond(Date date, int seconds) {
+		LocalDateTime localDateTime = date2localDateTime(date);
+		return localDateTime2date(localDateTime.plusSeconds(seconds));
+	}
+
+	public static String localDateTime2str(LocalDateTime localDateTime, String pattern) {
+		return localDateTime.format(DateTimeFormatter.ofPattern(pattern));
+	}
+
+	public static Date localDateTime2date(LocalDateTime localDateTime) {
+		return Date.from(localDateTime.atZone(UTC_PLUS_8).toInstant());
+	}
+
+	public static LocalDateTime date2localDateTime(Date date) {
+		return date.toInstant().atZone(UTC_PLUS_8).toLocalDateTime();
+	}
+
+	public static LocalDate date2localDate(Date date) {
+		return date.toInstant().atZone(UTC_PLUS_8).toLocalDate();
+	}
+
+	/**
+	 * 获取某个时间月份的天数
+	 *
+	 * @param date 时间
+	 * @return 返回月份的总天数
+	 */
+	public static int getDaysOfMonth(Date date) {
+		Calendar calendar = Calendar.getInstance();
+		calendar.setTime(date);
+		return calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
+	}
+
+	/**
+	 * 获取某年某月的天数
+	 *
+	 * @param year  年份
+	 * @param month 月份
+	 * @return 返回月份的总天数
+	 */
+	public static int getDaysOfMonth(int year, int month) {
+		Calendar calendar = Calendar.getInstance();
+		calendar.set(Calendar.MONTH, month - 1);
+		calendar.set(Calendar.YEAR, year);
+		return calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
+	}
+
+	public static boolean isSameDay(Date date1, Date date2) {
+		LocalDate localDate1 = date2localDate(date1);
+		LocalDate localDate2 = date2localDate(date2);
+		return localDate1.equals(localDate2);
+	}
+
+	/**
+	 * <p> 描述 : 调整日期时间</p>
+	 * <p> 备注 : hours 整数为几个小时后,负数几个小时前</p>
+	 *
+	 * @param date
+	 * @param hours
+	 * @return java.util.Date
+	 */
+	public static Date adjustDateTime(Date date, int hours) {
+		LocalDateTime localDateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
+		if (hours > 0) {
+			localDateTime = localDateTime.plusHours(hours);
+		} else {
+			localDateTime = localDateTime.minusHours(Math.abs(hours));
+		}
+		return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
+	}
+
+
+	/**
+	 * @return
+	 * @Title 想要获取的日期与传入日期的差值   比如想要获取传入日期前四天的日期 			day=-4即可
+	 * @Description
+	 * @author hang
+	 */
+	public static Date getSomeDay(Date date, int day) {
+		Calendar calendar = Calendar.getInstance();
+		calendar.setTime(date);
+		calendar.add(Calendar.DATE, day);
+		return getStartOfDay(calendar.getTime());
+	}
+
+
+	/**
+	 * @return
+	 * @Title 想要获取几个月前后的日期与传入日期的差值   比如想要获取传入日期前四天的日期
+	 * @Description
+	 * @author hang
+	 */
+	public static Date getMonthDiffDay(Date date, int day) {
+		Calendar calendar = Calendar.getInstance();
+		calendar.setTime(date);
+		calendar.add(Calendar.MONTH, day);
+		return getStartOfDay(calendar.getTime());
+	}
+
+	/**
+	 * @return
+	 * @Title 想要获取几个月前后的日期与传入日期的差值   比如想要获取传入日期前四天的日期
+	 * @Description
+	 * @author hang
+	 */
+	public static Date getYearDiffDay(Date date, int day) {
+		Calendar calendar = Calendar.getInstance();
+		calendar.setTime(date);
+		calendar.add(Calendar.YEAR, day);
+		return getStartOfDay(calendar.getTime());
+	}
+
+	/**
+	 * @param date
+	 * @return
+	 * @description: 获得当天最小时间
+	 * @author: jiang
+	 * @date: 2021年06月21日
+	 */
+	public static Date getStartOfDay(Date date) {
+		if( date != null ){
+			LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()),
+					ZoneId.systemDefault());
+			LocalDateTime startOfDay = localDateTime.with(LocalTime.MIN);
+			return Date.from(startOfDay.atZone(ZoneId.systemDefault()).toInstant());
+		}
+		return  null;
+	}
+
+	/**
+	 * @param date
+	 * @return
+	 * @description: 获得当天最大时间
+	 * @author: Jeff
+	 * @date: 2021年06月21日
+	 */
+	public static Date getEndOfDay(Date date) {
+		LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()),
+				ZoneId.systemDefault());
+		LocalDateTime endOfDay = localDateTime.with(LocalTime.MAX);
+		return Date.from(endOfDay.atZone(ZoneId.systemDefault()).toInstant());
+	}
+
+	/**
+	 * @return
+	 * @Title 计算两个日期的天数
+	 * @Description
+	 * @author hang
+	 */
+	public static int getDayDiffer(Date startDate, Date endDate) {
+		try {
+			SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+			long startDateTime = dateFormat.parse(dateFormat.format(startDate)).getTime();
+			long endDateTime = dateFormat.parse(dateFormat.format(endDate)).getTime();
+			return (int) ((endDateTime - startDateTime) / (1000 * 3600 * 24));
+		} catch (ParseException e) {
+			return 0;
+		}
+	}
+
+	public static Date getMonthFirstDay(Date thisDay, int diff) throws ParseException {
+		//时间字符串转 LocalDate 类型
+		LocalDate today = thisDay.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+		//当前月份+(-diff)
+		today = today.minusMonths(diff);
+		today = today.with(TemporalAdjusters.firstDayOfMonth());
+		SimpleDateFormat simple = new SimpleDateFormat("yyyy-MM-dd 00:00:00");
+		ZonedDateTime zonedDateTime = today.atStartOfDay(ZoneId.systemDefault());
+		String newday = simple.format(Date.from(zonedDateTime.toInstant()));
+		Date date = simple.parse(newday);
+		return date;
+	}
+
+	/**
+	 * 获取上个月指定第几天的日期(包括跨年)
+	 *
+	 * @param isEndDay   是否是最后一天  true默认上个月最后一天的日期,dayOfMonth随便传什么,false指定上个月某一天的日期
+	 * @param dayOfMonth 指定要获取的上个月的日期
+	 * @return 获取上个月最后一天的日期(包括跨年)
+	 */
+	public static Date getLastDayOfLastMonth(boolean isEndDay, int dayOfMonth) {
+		// 获取当前日期
+		LocalDate currentDate = LocalDate.now();
+		// 获取上个月的年份和月份
+		int lastMonthYear;
+		int lastMonth;
+		if (currentDate.getMonthValue() == 1) {
+			// 如果当前月份是1月,则上个月的年份为去年,月份为12
+			lastMonthYear = currentDate.getYear() - 1;
+			lastMonth = 12;
+		} else {
+			// 否则上个月的年份和月份分别为当前年份和当前月份减1
+			lastMonthYear = currentDate.getYear();
+			lastMonth = currentDate.getMonthValue() - 1;
+		}
+		// 构造上个月的YearMonth对象
+		YearMonth lastYearMonth = YearMonth.of(lastMonthYear, lastMonth);
+		LocalDate lastDayOfLastMonth;
+		if (isEndDay) {
+			// 获取上个月的最后一天
+			lastDayOfLastMonth = lastYearMonth.atEndOfMonth();
+		} else {
+			lastDayOfLastMonth = lastYearMonth.atDay(dayOfMonth);
+		}
+		return java.sql.Date.valueOf(lastDayOfLastMonth);
+	}
+
+	/**
+	 * 获取指定日期上个月指定第几天的日期(包括跨年)
+	 *
+	 * @param isEndDay   是否是最后一天  true默认上个月最后一天的日期,dayOfMonth随便传什么,false指定上个月某一天的日期
+	 * @param dayOfMonth 获取指定日期的上个月的日期
+	 * @param date       指定日期
+	 * @return 获取上个月最后一天的日期(包括跨年)
+	 */
+	public static Date getLastDayOfLastMonth(boolean isEndDay, int dayOfMonth, Date date) {
+		LocalDate currentDate = date.toInstant().atZone(UTC_PLUS_8).toLocalDate();
+		// 获取上个月的年份和月份
+		int lastMonthYear;
+		int lastMonth;
+		if (currentDate.getMonthValue() == 1) {
+			// 如果当前月份是1月,则上个月的年份为去年,月份为12
+			lastMonthYear = currentDate.getYear() - 1;
+			lastMonth = 12;
+		} else {
+			// 否则上个月的年份和月份分别为当前年份和当前月份减1
+			lastMonthYear = currentDate.getYear();
+			lastMonth = currentDate.getMonthValue() - 1;
+		}
+		// 构造上个月的YearMonth对象
+		YearMonth lastYearMonth = YearMonth.of(lastMonthYear, lastMonth);
+		LocalDate lastDayOfLastMonth;
+		if (isEndDay) {
+			// 获取上个月的最后一天
+			lastDayOfLastMonth = lastYearMonth.atEndOfMonth();
+		} else {
+			lastDayOfLastMonth = lastYearMonth.atDay(dayOfMonth);
+		}
+		return java.sql.Date.valueOf(lastDayOfLastMonth);
+	}
+
+
+	/**
+	 * 获取当月某天
+	 *
+	 * @param day
+	 * @return
+	 */
+	public static Date getDayOfMonth(int day) {
+		// 获取当前日期
+		LocalDate currentDate = LocalDate.now();
+		// 设置日期为当月的第15天
+		LocalDate fifteenthDayOfMonth = LocalDate.of(currentDate.getYear(), currentDate.getMonth(), day);
+		return java.sql.Date.valueOf(fifteenthDayOfMonth);
+	}
+
+	/**
+	 * 上个月月份
+	 *
+	 * @return 上个月月份
+	 */
+	public static int getLastMonth() {
+		// 获取当前日期
+		LocalDate currentDate = LocalDate.now();
+		// 获取上个月的日期
+		LocalDate lastMonthDate = currentDate.minusMonths(1);
+		// 获取上个月的月份
+		int lastMonth = lastMonthDate.getMonthValue();
+		return lastMonth;
+	}
+
+	/**
+	 * 判断当天是否是多少号
+	 *
+	 * @param mark 号
+	 * @return 判断当天是否是多少号
+	 */
+	public static Boolean isMark(int mark) {
+		Calendar calendar = Calendar.getInstance();
+		// 获取日期的日
+		int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
+		// 判断当天是否是多少号
+		return dayOfMonth == mark;
+	}
+
+	/**
+	 * 指定日期月份第一天
+	 *
+	 * @param date 指定日期
+	 * @return 指定日期月份第一天
+	 */
+	public static Date getFirstDayOfMonth(Date date) {
+		Calendar calendar = Calendar.getInstance();
+		calendar.setTime(date);
+		calendar.set(Calendar.DAY_OF_MONTH, 1);
+		calendar.set(Calendar.HOUR_OF_DAY, 0);
+		calendar.set(Calendar.MINUTE, 0);
+		calendar.set(Calendar.SECOND, 0);
+		calendar.set(Calendar.MILLISECOND, 0);
+		return calendar.getTime();
+	}
+
+	/**
+	 * 指定日期月份最后一天
+	 *
+	 * @param date 指定日期
+	 * @return 指定日期月份最后一天
+	 */
+	public static Date getLastDayOfMonth(Date date) {
+		Calendar calendar = Calendar.getInstance();
+		calendar.setTime(date);
+		calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
+		calendar.set(Calendar.HOUR_OF_DAY, 23);
+		calendar.set(Calendar.MINUTE, 59);
+		calendar.set(Calendar.SECOND, 59);
+		calendar.set(Calendar.MILLISECOND, 999);
+		return calendar.getTime();
+	}
+
+	/**
+	 * @param yearMonth 年月 2024-01
+	 * @return 获取月份天数
+	 */
+	public static int getDaysInMonth(String yearMonth) {
+		YearMonth ym = YearMonth.parse(yearMonth);
+		Month month = ym.getMonth();
+		if (month == Month.FEBRUARY && ym.isLeapYear()) {
+			return 29;
+		} else {
+			return month.maxLength();
+		}
+	}
+
+	public static DynamicObject getPeriodByDate(Date date) {
+		QFilter filter = new QFilter("begindate", "<=", date).and("enddate", ">=", date);
+		return BusinessDataServiceHelper.loadSingle("bd_period", filter.toArray());
+	}
+
+	public static String convertTimestamp(long timestamp) {
+		// 转换为秒
+		long totalSeconds = timestamp;
+		// 计算小时
+		long hours = totalSeconds / 3600;
+		// 计算分钟
+		long minutes = (totalSeconds % 3600) / 60;
+		// 计算秒
+		long seconds = totalSeconds % 60;
+
+		return String.format("%02d:%02d:%02d", hours, minutes, seconds);
+	}
+
+	/**
+	 * 取最大值
+	 * @param d1 d1
+	 * @param d2 d2
+	 * @return 最大值
+	 */
+	public static Date max(Date d1, Date d2) {
+		if (d1 == null) {
+			return d2;
+		}
+		if (d2 == null) {
+			return d1;
+		}
+		if (d1.after(d2)) {
+			return d1;
+		}else {
+			return d2;
+		}
+	}
+
+	/**
+	 * 取最小值
+	 * @param d1 d1
+	 * @param d2 d2
+	 * @return 最小值
+	 */
+	public static Date min(Date d1, Date d2) {
+		if (d1 == null) {
+			return d2;
+		}
+		if (d2 == null) {
+			return d1;
+		}
+		if (d1.before(d2)) {
+			return d1;
+		}else {
+			return d2;
+		}
+	}
+}

+ 104 - 0
nckd-pur/src/main/java/nckd/pur/scp/common/constant/BaseFieldConst.java

@@ -0,0 +1,104 @@
+package nckd.pur.scp.common.constant;
+
+/**
+ * 基本字段标识(苍穹默认字段标识)
+ *
+ * @date 2024/3/31
+ */
+public class BaseFieldConst {
+    /**表单主键*/
+    public static final String ID = "id";
+    /**表单主键*/
+    public static final String PID = ".id";
+    /** 多选基础资料对象 */
+    public static final String FBASEDATAID = "fbasedataid";
+    /** 多选基础资料ID */
+    public static final String FBASEDATAID_ID = "fbasedataid_id";
+    /**主数据内码 */
+    public static final String MASTER_ID = "masterid";
+    /** 可用状态 */
+    public static final String ENABLE = "enable";
+    /** 基础资料编码 */
+    public static final String NUMBER = "number";
+
+    /** 基础资料编码 */
+    public static final String PNUMBER = ".number";
+    /** 基础资料名称 */
+    public static final String NAME = "name";
+    /** 基础资料名称 */
+    public static final String PNAME = ".name";
+    /** 基础资料状态 */
+    public static final String STATUS="status";
+
+    /** 树形基础资料-长编码 */
+    public static final String LONG_NUMBER = "longnumber";
+    /** 树形基础资料-长名称 */
+    public static final String FULL_NAME = "fullname";
+    /** 树形基础资料-上级组织 */
+    public static final String PARENT = "parent";
+    /** 树形基础资料-是否叶子 */
+    public static final String IS_LEAF = "isleaf";
+    /** 树形基础资料-级次 */
+    public static final String LEVEL = "level";
+
+    /** 单据编码 */
+    public static final String BILL_NO = "billno";
+    /** 单据编码 */
+    public static final String PBILLNO = ".billno";
+    /** 单据状态 */
+    public static final String BILL_STATUS = "billstatus";
+
+    /** 币别 */
+    public static final String CURRENCY = "currency";
+    /** 金额币种精度 */
+    public static final String AMT_PRECISION = "amtprecision";
+    /** 单价币种精度 */
+    public static final String PRICE_PRECISION = "priceprecision";
+
+    /** 创建人 */
+    public static final String CREATOR = "creator";
+    /** 创建时间 */
+    public static final String CREATE_TIME = "createtime";
+    /** 所属组织 */
+    public static final String ORG = "org";
+    /** 创建组织*/
+    public static final String CREATE_ORG = "createorg";
+    /** 业务组织*/
+    public static final String USE_ORG = "useorg";
+    /** 修改人 */
+    public static final String MODIFIER = "modifier";
+    /** 修改时间 */
+    public static final String MODIFY_TIME = "modifytime";
+    /** 审核人 */
+    public static final String AUDITOR = "auditor";
+    /** 审核时间 */
+    public static final String AUDIT_DATE = "auditdate";
+    /** 禁用人 */
+    public static final String DISABLER = "disabler";
+    /** 禁用时间 */
+    public static final String DISABLE_DATE = "disabledate";
+    /** 分录序号 */
+    public static final String SEQ = "seq";
+
+    /** 附件面板 */
+    public static final String ATTACHMENTPANEL = "attachmentpanel";
+
+    /** 默认分录名 */
+    public static final String ENTRY_ENTITY = "entryentity";
+    /** 默认子单据体分录名 */
+    public static final String SUB_ENTRY_ENTITY = "subentryentity";
+    /** 默认单据列表 */
+    public static final String BILL_LIST_AP = "billlistap";
+    /** 默认工具栏 */
+    public static final String TOOL_BAR_AP = "toolbarap";
+    /** 默认高级面板工具栏 */
+    public static final String ADVCONTOOLBARAP = "advcontoolbarap";
+
+    /** 猪场 */
+    public static final String PIGFARM = "pigfarm";
+    /** 服务部 */
+    public static final String SRVFILE = "srvfile";
+    /** 单据体列表标识 */
+    public static final String BILLLISTAP = "billlistap";
+
+}

+ 433 - 0
nckd-pur/src/main/java/nckd/pur/scp/common/utils/FormUtils.java

@@ -0,0 +1,433 @@
+package nckd.pur.scp.common.utils;
+
+import kd.bos.bill.BillShowParameter;
+import kd.bos.bill.OperationStatus;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.dataentity.entity.LocaleString;
+import kd.bos.entity.datamodel.IDataModel;
+import kd.bos.entity.datamodel.ListSelectedRow;
+import kd.bos.exception.KDBizException;
+import kd.bos.ext.form.control.MapControl;
+import kd.bos.ext.form.dto.MapSelectPointOption;
+import kd.bos.form.*;
+import kd.bos.form.container.Container;
+import kd.bos.form.control.AbstractGrid;
+import kd.bos.form.control.Control;
+import kd.bos.form.control.EntryGrid;
+import kd.bos.form.events.BillListHyperLinkClickEvent;
+import kd.bos.form.events.HyperLinkClickListener;
+import kd.bos.form.field.*;
+import kd.bos.form.field.events.BeforeF7SelectListener;
+import kd.bos.form.plugin.AbstractFormPlugin;
+import kd.bos.list.BillList;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import nckd.pur.scp.common.constant.BaseFieldConst;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.*;
+
+/**
+ * 表单工具类
+ * @author XUYC
+ * @since 2025/2/25
+ */
+public final class FormUtils {
+    private FormUtils() {}
+
+    /**经度**/
+    public static final String SBT_LONGITUDE = "sbt_longitude";
+    /**维度**/
+    public static final String SBT_LATITUDE = "sbt_latitude";
+
+    /**
+     * 添加按钮监听
+     * @param form form
+     * @param propKeys 控件标识
+     */
+    public static void addClickListeners(AbstractFormPlugin form, String... propKeys) {
+        for(String propKey : propKeys) {
+            Control c = form.getControl(propKey);
+            if(c != null) {
+                form.addClickListeners(propKey);
+            }
+        }
+    }
+
+
+    /**
+     * 添加且实现超链接监听
+     * @param form form
+     * @param entryNames 列表标识
+     */
+    public static void addAndImplHyperClickListener(AbstractFormPlugin form, String... entryNames) {
+        for(String name : entryNames) {
+            AbstractGrid control = form.getControl(name);
+            if (control == null) {
+               continue;
+            }
+            control.addHyperClickListener(event -> {
+                String fieldName = event.getFieldName();
+                if (StringUtils.equalsAny(fieldName, BaseFieldConst.NUMBER, BaseFieldConst.BILL_NO, BaseFieldConst.NAME)) {
+                    BillList source = (BillList) event.getSource();
+                    String entityId = source.getEntityId();
+                    ListSelectedRow selectedRows = ((BillListHyperLinkClickEvent) event).getCurrentRow();
+                    // 点击链接,打开页面
+                    FormUtils.openBillEditPage(form.getView(), entityId, selectedRows.getPrimaryKeyValue());
+                }
+            });
+        }
+    }
+
+    public static void addHyperClickListener(HyperLinkClickListener form, String... entryNames) {
+        if (form instanceof AbstractFormPlugin) {
+            AbstractFormPlugin localForm = (AbstractFormPlugin)form;
+            for(String name : entryNames) {
+                AbstractGrid control = localForm.getControl(name);
+                if (control == null) {
+                    continue;
+                }
+                control.addHyperClickListener(form);
+            }
+        } else {
+            throw new KDBizException("插件不是kd.bos.form.plugin.AbstractFormPlugin子类,不能使用FormUtils.addF7Listener");
+        }
+    }
+
+
+    /**
+     * 添加BeforeF7SelectListener
+     * @param form form
+     * @param f7Names f7字段标识
+     */
+    public static void addF7Listener(BeforeF7SelectListener form, String... f7Names) {
+        if (form instanceof AbstractFormPlugin) {
+            AbstractFormPlugin localForm = (AbstractFormPlugin)form;
+            for(String f7Name : f7Names) {
+                Control f7 = localForm.getControl(f7Name);
+                if (f7 != null) {
+                    if (f7 instanceof BasedataEdit) {
+                        ((BasedataEdit)f7).addBeforeF7SelectListener(form);
+                    } else if (f7 instanceof RefBillEdit) {
+                        ((RefBillEdit)f7).addBeforeF7SelectListener(form);
+                    }
+                }
+            }
+        } else {
+            throw new KDBizException("插件不是kd.bos.form.plugin.AbstractFormPlugin子类,不能使用FormUtils.addF7Listener");
+        }
+    }
+
+    /**
+     * 清空值
+     * @param view view
+     * @param props 字段标识
+     */
+    public static void cleanValue(IFormView view, String... props) {
+        IDataModel model = view.getModel();
+        for (String prop : props) {
+            Control control = view.getControl(prop);
+            if (control instanceof BasedataPropEdit){
+                continue;
+            }
+            if (control instanceof Container) {
+                if (control instanceof EntryGrid) {
+                    model.deleteEntryData(prop);
+                }else {
+                    List<Control> items = ((Container) control).getItems();
+                    for (Control item : items) {
+                        cleanValue(view, item.getKey());
+                    }
+                }
+            }else {
+                model.setValue(prop, null);
+            }
+        }
+    }
+
+    /**
+     * 清空值
+     * @param view view
+     * @param rowIndex 分录下标
+     * @param props 字段标识
+     */
+    public static void cleanValue(IFormView view, int rowIndex,  String... props) {
+        IDataModel model = view.getModel();
+        for (String prop : props) {
+            Control control = view.getControl(prop);
+            if (control instanceof Container) {
+                List<Control> items = ((Container) control).getItems();
+                for (Control item : items) {
+                    cleanValue(view, rowIndex, item.getKey());
+                }
+            }else {
+                model.setValue(prop, null, rowIndex);
+            }
+        }
+    }
+
+    /**
+     * 设置控件是否显示、清空值
+     * @param view view
+     * @param show 是否显示
+     * @param cleanValueIfHide 当字段隐藏时,是否清空值
+     * @param props props
+     */
+    public static void setVisible(IFormView view, boolean show, boolean cleanValueIfHide, String... props) {
+        view.setVisible(show, props);
+        if (cleanValueIfHide && !show) {
+            cleanValue(view, props);
+        }
+    }
+
+    /**
+     * 设置控件是否显示、清空值
+     * @param view view
+     * @param show 是否显示
+     * @param props props
+     */
+    public static void setVisible(IFormView view, boolean show, String... props) {
+        setVisible(view, show,false, props);
+    }
+
+    /**
+     * 设置控件是否显示、必录、清空值
+     * @param view view
+     * @param showAndMustInput 是否显示、必录
+     * @param cleanValueIfHide 当字段隐藏时,是否清空值
+     * @param props props
+     */
+    public static void setVisibleAndMustInput(IFormView view, boolean showAndMustInput, boolean cleanValueIfHide, String... props) {
+        view.setVisible(showAndMustInput, props);
+        if (cleanValueIfHide && !showAndMustInput) {
+            cleanValue(view, props);
+        }
+        setMustInput(view, showAndMustInput, props);
+    }
+
+    /**
+     * 设置控件是否显示、必录
+     * @param view view
+     * @param showAndMustInput 是否显示、必录
+     * @param props 字段标识
+     */
+    public static void setVisibleAndMustInput(IFormView view, boolean showAndMustInput, String... props) {
+        view.setVisible(showAndMustInput, props);
+        setMustInput(view, showAndMustInput, props);
+    }
+
+
+    /**
+     * 设置控件是否必录
+     * @param view view
+     * @param b 是否必录
+     * @param props 字段标识
+     */
+    public static void setMustInput(IFormView view, boolean b, String... props) {
+        for (String prop : props) {
+            Control control = view.getControl(prop);
+            if (control instanceof FieldEdit) {
+                // 前端加星号,前端不一定会校验
+                ((FieldEdit) control).setMustInput(b);
+            }
+        }
+    }
+
+
+    /**
+     * 更改日期可选范围
+     * @param view view
+     * @param minDate 日期
+     * @param maxDate 日期
+     * @param props 字段标识
+     */
+    public static void changeDateRange(IFormView view, Date minDate, Date maxDate, String... props) {
+        for (String prop : props) {
+            Control control = view.getControl(prop);
+            if (control instanceof DateTimeEdit) {
+                DateTimeEdit obj = (DateTimeEdit)control;
+                obj.setMinDate(minDate);
+                obj.setMaxDate(maxDate);
+            }else if (control instanceof DateRangeEdit) {
+                DateRangeEdit obj = (DateRangeEdit)control;
+                obj.setMinDate(minDate);
+                obj.setMaxDate(maxDate);
+            }
+        }
+    }
+
+    /**
+     * 打开表单
+     * @param view view
+     * @param formId 页面标识
+     * @param customParamsMap 自定义参数
+     */
+    public static void openFormPage(IFormView view, String formId, Map<String, Object> customParamsMap) {
+        openFormPage(view, formId, customParamsMap, ShowType.Modal);
+    }
+
+    /**
+     * 打开表单
+     * @param view view
+     * @param formId 页面标识
+     * @param customParamsMap 自定义参数
+     * @param showType 展示类型
+     */
+    public static void openFormPage(IFormView view, String formId, Map<String, Object> customParamsMap, ShowType showType) {
+        FormShowParameter formShowParameter = new FormShowParameter();
+        formShowParameter.setFormId(formId);
+        formShowParameter.setCustomParams(customParamsMap);
+        formShowParameter.getOpenStyle().setShowType(showType);
+        view.showForm(formShowParameter);
+    }
+
+    /**
+     * 打开单据编辑页面
+     * @param view view
+     * @param billName 单据标识
+     * @param pkId 主键id
+     */
+    public static void openBillEditPage(IFormView view, String billName, Object pkId) {
+        openBillEditPage(view, billName, pkId, OperationStatus.VIEW);
+    }
+
+    /**
+     * 打开单据编辑页面
+     * @param view view
+     * @param billName 单据标识
+     * @param pkId 主键id
+     * @param status 单据操作状态
+     */
+    public static void openBillEditPage(IFormView view, String billName, Object pkId, OperationStatus status) {
+        if (ObjectUtils.isEmpty(billName) || ObjectUtils.isEmpty(pkId)) {
+            view.showTipNotification("单据标识或者ID为空。");
+        } else if ((pkId instanceof Long) && (Long) pkId == 0) {
+            view.showTipNotification("单据ID为空。");
+        } else {
+            view.showForm(getShowBillParam(billName, pkId, status));
+        }
+    }
+
+    public static BillShowParameter getShowBillParam(String formId, Object pkId, OperationStatus status) {
+        BillShowParameter param = new BillShowParameter();
+        param.getOpenStyle().setShowType(ShowType.MainNewTabPage);
+        param.setFormId(formId);
+        if (status != null) {
+            param.setStatus(status);
+        }
+        if (!ObjectUtils.isEmpty(pkId)) {
+            if ((pkId instanceof Long) && (Long) pkId != 0) {
+                param.setPkId(pkId);
+            }
+            if (pkId instanceof String) {
+                param.setPkId(pkId);
+            }
+        }
+        return param;
+    }
+
+    public static BillShowParameter getShowBillParam(String formId, Object pkId) {
+        return getShowBillParam(formId, pkId, null);
+    }
+
+
+    /**
+     * 获取分录基础资料去重QFilter
+     * @param model model
+     * @param entryName 分录标识
+     * @param fieldName 基础资料字段标识
+     * @return QFilter
+     */
+    public static QFilter getEntryF7UniqueFilter(IDataModel model, String entryName, String fieldName) {
+        List<Object> ids = new ArrayList<>();
+        DynamicObjectCollection entryEntity = model.getEntryEntity(entryName);
+        for (DynamicObject row : entryEntity) {
+            DynamicObject item = row.getDynamicObject(fieldName);
+            if (item != null) {
+                ids.add(item.getPkValue());
+            }
+        }
+        return new QFilter(BaseFieldConst.ID, QCP.not_in, ids);
+    }
+
+    /**
+     * 获取子分录基础资料去重QFilter
+     * @param model model
+     * @param entryName 分录标识
+     * @param subEntryName 子分录标识
+     * @param fieldName 基础资料字段标识
+     * @return QFilter
+     */
+    public static QFilter getSubEntryF7UniqueFilter(IDataModel model, String entryName, String subEntryName, String fieldName) {
+        List<Object> ids = new ArrayList<>();
+        int pRowIndex = model.getEntryCurrentRowIndex(entryName);
+        DynamicObject bill = model.getDataEntity(true);
+        DynamicObjectCollection srvFileEt = bill.getDynamicObjectCollection(entryName);
+        DynamicObject row = srvFileEt.get(pRowIndex);
+        DynamicObjectCollection subEntry = row.getDynamicObjectCollection(subEntryName);
+        for (DynamicObject subRow : subEntry) {
+            DynamicObject item = subRow.getDynamicObject(fieldName);
+            if (item != null) {
+                ids.add(item.getPkValue());
+            }
+        }
+        return new QFilter(BaseFieldConst.ID, QCP.not_in, ids);
+    }
+
+
+
+    /**
+     * 添加字段控件命名后缀
+     * @param view view
+     * @param suffix 后缀
+     * @param fieldNames 字段标识
+     */
+    public static void addFieldControlCaptionSuffix(IFormView view, String suffix, String... fieldNames) {
+        for (String field : fieldNames) {
+            FieldEdit control = view.getControl(field);
+            if (control == null) {
+                continue;
+            }
+            String name = control.getProperty().getDisplayName().toString();
+            if (ObjectUtils.isEmpty(suffix)) {
+                control.setCaption(new LocaleString(name));
+            }else {
+                String newName = name + "(" + suffix + ")";
+                control.setCaption(new LocaleString(newName));
+            }
+        }
+    }
+
+    /**
+     * 放在bindData方法后
+     * 设置地图控件拖动,并赋值
+     * @param model
+     * @param view
+     * @param mapControll
+     */
+    public static void setMapStatusAndDataByBind(IDataModel model, IFormView view, String mapControll){
+        // 设置是否可拖动地图标记*
+        Map<String, Object> dataMap = new HashMap<>();
+        dataMap.put("canMarkerDraggble", true);
+        dataMap.put("k", mapControll);
+        IClientViewProxy proxy = view.getService(IClientViewProxy.class);
+        proxy.addAction(ClientActions.updateControlStates, dataMap);
+
+        //经度
+        String longitude = (String) model.getValue("login");
+        //维度
+        String latitude = (String) model.getValue("latu");
+        //地图控件
+        if(StringUtils.isNotEmpty(longitude) && StringUtils.isNotEmpty(latitude)) {
+            MapControl mapControl = view.getControl(mapControll);
+            MapSelectPointOption mapSelect = new MapSelectPointOption();
+            mapSelect.setLat(Double.parseDouble(latitude));
+            mapSelect.setLng(Double.parseDouble(longitude));
+            //设置地图
+            mapControl.selectPoint(mapSelect);
+        }
+    }
+
+}

+ 122 - 0
nckd-pur/src/main/java/nckd/pur/scp/mservice/SendMsgToWeChatOA.java

@@ -0,0 +1,122 @@
+package nckd.pur.scp.mservice;
+
+import com.alibaba.fastjson.JSONObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.exception.KDBizException;
+import kd.bos.logging.Log;
+import kd.bos.logging.LogFactory;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.QueryServiceHelper;
+import kd.bos.servicehelper.parameter.SystemParamServiceHelper;
+import kd.bos.util.HttpUtils;
+import kd.bos.util.StringUtils;
+import kd.bos.workflow.engine.msg.AbstractMessageServiceHandler;
+import kd.bos.workflow.engine.msg.ctx.MessageContext;
+import kd.bos.workflow.engine.msg.info.MessageInfo;
+import kd.bos.workflow.engine.msg.info.ToDoInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class SendMsgToWeChatOA extends AbstractMessageServiceHandler {
+    private Log log = LogFactory.getLog(SendMsgToWeChatOA.class);
+
+    @Override
+    public void createToDo(MessageContext messageContext, ToDoInfo toDoInfo) {
+
+    }
+
+    @Override
+    public void dealToDo(MessageContext messageContext, ToDoInfo toDoInfo) {
+
+    }
+
+    @Override
+    public void deleteToDo(MessageContext messageContext, ToDoInfo toDoInfo) {
+
+    }
+
+    @Override
+    public void sendMessage(MessageContext ctx, MessageInfo message) {
+        super.sendMessage(ctx, message);
+        String mobUrl = message.getMobContentUrl();
+        String contentUrl = message.getContentUrl();
+        Long id = message.getId();
+        String title = message.getMessageTitle().getLocaleValue_zh_CN();
+        String content = message.getMessageContent().getLocaleValue_zh_CN();
+        Map<String, Object> publicParamWhole = SystemParamServiceHelper.loadPublicParametersFromCache();
+        //微信获取token地址
+        String url = publicParamWhole.get("tokenurl").toString();
+        //公众号appid
+        String gzhappid = publicParamWhole.get("appid").toString();
+        //公众号密码
+        String gzhappsecret = publicParamWhole.get("appsecret").toString();
+        //公众号模板消息 模板id
+        String templateid = publicParamWhole.get("templateid").toString();
+        url = url+"&appid="+gzhappid+"&secret="+gzhappsecret;
+        String accessToken = "";
+        //获取token
+        String responseStr = HttpUtils.request(url);
+        if(!StringUtils.isEmpty(responseStr)){
+            JSONObject jsonObject = JSONObject.parseObject(responseStr);
+            accessToken = jsonObject.getString("access_token");
+            if(StringUtils.isEmpty(accessToken)){
+                String errmsg = jsonObject.getString("errmsg");
+            }
+        }
+        List<Long> userIds = message.getUserIds();
+        List<Long> errUserIds = new ArrayList<Long>();
+        if(!StringUtils.isEmpty(accessToken)){
+            //模板消息url
+            url = publicParamWhole.get("mburl").toString();
+            url = url+accessToken;
+            JSONObject requestJson = new JSONObject();
+            requestJson.put("template_id",templateid);
+            requestJson.put("url",mobUrl);
+            requestJson.put("client_msg_id",id);
+            JSONObject dataJson = JSONObject.parseObject(content);
+            requestJson.put("data",dataJson);
+            for(Long userId:userIds){
+                String openid = getUserOpenId(userId);
+                if(StringUtils.isEmpty(openid)){
+                    continue;
+                }
+                requestJson.put("touser",openid);
+                String response = HttpUtils.post(url, null, requestJson.toJSONString());
+                log.info("公众号:"+response);
+                if(!StringUtils.isEmpty(response)){
+                    errUserIds.add(userId);
+                }else{
+                    JSONObject json = JSONObject.parseObject(response);
+                    Integer errcode = json.getInteger("errcode");
+                    if(errcode!=0){
+                        errUserIds.add(userId);
+                    }
+                }
+            }
+            if(errUserIds.size()>0){
+                message.setUserIds(errUserIds);
+                throw new KDBizException("推送公众号消息失败,请联系管理员!");
+            }
+
+        }else{
+            throw new KDBizException("推送公众号消息失败,获取token失败!");
+        }
+
+
+    }
+    //根据用户id获取微信openid
+    private String getUserOpenId(Long userId) {
+        String openid = "";
+        //普通微信表里存了1
+        QFilter filter = new QFilter("imtype", QCP.equals,"1");
+        filter = filter.and(new QFilter("userid", QCP.equals,userId));
+        DynamicObjectCollection openids = QueryServiceHelper.query("bas_immapping", "openid", new QFilter[]{filter});
+        if(openids.size()>0){
+            openid = openids.get(0).getString("openid");
+        }
+        return openid;
+    }
+}

+ 36 - 0
nckd-pur/src/main/java/nckd/pur/scp/plugin/form/PurApplyBillPlugin.java

@@ -0,0 +1,36 @@
+package nckd.pur.scp.plugin.form;
+
+import kd.bos.bill.AbstractBillPlugIn;
+import kd.bos.form.field.events.BeforeF7SelectEvent;
+import kd.bos.form.field.events.BeforeF7SelectListener;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import nckd.pur.scp.common.utils.FormUtils;
+
+import java.util.EventObject;
+
+/**
+ * 单据界面插件
+ */
+public class PurApplyBillPlugin extends AbstractBillPlugIn implements BeforeF7SelectListener {
+
+    @Override
+    public void registerListener(EventObject e) {
+        super.registerListener(e);
+        FormUtils.addF7Listener(this,"material");
+    }
+
+    @Override
+    public void beforeF7Select(BeforeF7SelectEvent evt) {
+        String fieldName = evt.getProperty().getName();
+        if(fieldName.equals("material")) {
+            boolean jjcg = (boolean) this.getModel().getValue("nckd_jjcg");
+            boolean xgm = (boolean) this.getModel().getValue("nckd_xgm");
+            String value = (String)this.getModel().getValue("nckd_combofield");
+            if (jjcg || xgm || "1".equals(value)) {
+                QFilter mtQf = new QFilter("masterid.nckd_centralized",QCP.equals,true);
+                evt.getCustomQFilters().add(mtQf);
+            }
+        }
+    }
+}

+ 59 - 0
nckd-pur/src/main/java/nckd/pur/scp/plugin/operate/SaloutStockOpPlugin.java

@@ -0,0 +1,59 @@
+package nckd.pur.scp.plugin.operate;
+
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.entity.plugin.AbstractOperationServicePlugIn;
+import kd.bos.entity.plugin.args.AfterOperationArgs;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.botp.BFTrackerServiceHelper;
+import kd.bos.servicehelper.operation.SaveServiceHelper;
+import kd.sdk.plugin.Plugin;
+import nckd.pur.scp.common.DateUtil;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 单据操作插件
+ */
+public class SaloutStockOpPlugin extends AbstractOperationServicePlugIn{
+
+    @Override
+    public void afterExecuteOperationTransaction(AfterOperationArgs e) {
+        super.afterExecuteOperationTransaction(e);
+        DynamicObject[] entities = e.getDataEntities();
+        String operationKey = e.getOperationKey();
+        if("submit".equals(operationKey)){
+            for (DynamicObject bill : entities) {
+                String wuliu = bill.getString("nckd_textfield");
+                String date = DateUtil.date2str(bill.getDate("delidate"),DateUtil.DATE_FORMAT_YYYY_MM_DD);
+                String content = String.format("已发货,预计到货日期:%s,物流单号:%s",date,wuliu);
+                //反写至 采购订单 采购申请单
+                DynamicObjectCollection materialEntry = bill.getDynamicObjectCollection("materialentry");
+                //采购订单编号
+                List<String> poBillNo = materialEntry.stream().map(entry -> entry.getString("pobillno")).distinct().collect(Collectors.toList());
+                //采购订单
+                DynamicObject purOrder = BusinessDataServiceHelper.loadSingle("pm_purorderbill", new QFilter("billno", QCP.in, poBillNo).toArray());
+                //采购申请单
+                Map<String, HashSet<Long>> srcBills = BFTrackerServiceHelper.findSourceBills("pm_purorderbill", new Long[]{purOrder.getLong("id")});
+                DynamicObject purApply = null;
+                if(!srcBills.isEmpty()){
+                    if (srcBills.containsKey("pm_purapplybill")) {
+                        ArrayList list = new ArrayList(srcBills.get("pm_purapplybill"));
+                        purApply = BusinessDataServiceHelper.loadSingle(list.get(0),"pm_purapplybill");
+                        purApply.set("nckd_tracking",content);
+                    }
+                }
+                purOrder.set("·",content);
+                if(purApply!=null) {
+                    SaveServiceHelper.save(new DynamicObject[]{purOrder});
+                    SaveServiceHelper.save(new DynamicObject[]{purApply});
+                }else{
+                    SaveServiceHelper.save(new DynamicObject[]{purOrder});
+                }
+            }
+        }
+    }
+}

+ 63 - 0
nckd-pur/src/main/java/nckd/pur/scp/plugin/operate/SendMsgToPurPersonExt.java

@@ -0,0 +1,63 @@
+package nckd.pur.scp.plugin.operate;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import kd.bos.bec.model.KDBizEvent;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.botp.BFTrackerServiceHelper;
+import kd.scm.scp.common.util.SendMsgToPurPerson;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @Author:Zt
+ * @Date:2025/11/26
+ **/
+public class SendMsgToPurPersonExt extends SendMsgToPurPerson {
+
+    @Override
+    public List<Long> calcUserIds(KDBizEvent evt) {
+        return super.calcUserIds(evt);
+    }
+
+    @Override
+    public Map<String, List<Long>> calculateUserIds(KDBizEvent evt) {
+        Map<String, List<Long>> map  = super.calculateUserIds(evt);
+        JSONArray data = JSONArray.parseArray(evt.getSource());
+        JSONObject jsonObj = data.getJSONObject(0);
+        String billId = jsonObj.getString("businesskey");
+        List<Long> list = new ArrayList();
+
+
+        //发货单
+        DynamicObject bill = BusinessDataServiceHelper.loadSingle(Long.valueOf(billId), "scp_saloutstock");
+        //查询 采购订单 采购申请单
+        DynamicObjectCollection materialEntry = bill.getDynamicObjectCollection("materialentry");
+        //采购订单编号
+        List<String> poBillNo = materialEntry.stream().map(entry -> entry.getString("pobillno")).distinct().collect(Collectors.toList());
+        //采购订单
+        DynamicObject purOrder = BusinessDataServiceHelper.loadSingle("pm_purorderbill", new QFilter("billno", QCP.in, poBillNo).toArray());
+        //采购申请单
+        Map<String, HashSet<Long>> srcBills = BFTrackerServiceHelper.findSourceBills("pm_purorderbill", new Long[]{purOrder.getLong("id")});
+        if(!srcBills.isEmpty()) {
+            if (srcBills.containsKey("pm_purapplybill")) {
+                Long purApplyId = srcBills.get("pm_purapplybill").iterator().next();
+                long purApplyUser = BusinessDataServiceHelper.loadSingle(purApplyId,"pm_purapplybill").getLong("creator.id");
+                list.add(purApplyUser);
+            }
+        }
+        list.add(purOrder.getLong("creator.id"));
+
+        map.put(bill.getPkValue().toString(), list);
+
+        return map;
+    }
+}

+ 144 - 0
readme.md

@@ -0,0 +1,144 @@
+# 金蝶云苍穹Gradle模板使用说明
+
+## 1.启动服务
+
+### 1) DebugApplication中设置好环境必要的参数
+
+- setClusterNumber: 集群编码
+- setTenantNumber: 租户编码
+- setMcServerUrl: 设置MC服务地址
+- setCosmicWepPort: 设置苍穹服务端口
+- setConfigUrl: 设置zk服务地址及用户密码
+- setFsServerUrl: 设置文件服务地址
+- setImageServerUrl: 设置图像服务地址
+
+
+例:
+
+```java
+cosmic.setClusterNumber("cosmic");
+cosmic.setTenantNumber("sample");
+cosmic.setMcServerUrl("http://127.0.0.1:8090");
+cosmic.setCosmicWepPort(8080)
+cosmic.setConfigUrl("127.0.0.1:2182?user=zk&password=xxxxxx");
+cosmic.setFsServerUrl("127.0.0.1", 8100);
+cosmic.setImageServerUrl("127.0.0.1", 8100);
+```
+
+
+### 2) 启动服务
+
+```java
+kd.cosmic.DebugApplication
+```
+首次运行,请检查项目默认的JDK,确保为1.8版本,否则会提示启动异常。
+
+
+### 3) 登录
+启动完毕后打开:http://127.0.0.1:8080/ierp
+
+注意:此处的端口为setCosmicWepPort所配置的具体端口数值
+
+
+## 2.更新环境
+菜单操作路径:苍穹开发助手-->更新环境
+
+将从MC服务器上更新最新包:cosmic.zip、webapp.zip,解压到统一的苍穹资源目录下,可通过系统环境变量"COSMIC_HOME"或项目中gradle.properties的"systemProp.cosmic_home"配置项指定。
+
+默认的web静态资源目录为:System.getenv("COSMIC_HOME") + "/static-file-service",也可通过以下代码修改:
+
+```java
+cosmic.setWebResPath("xxx/static-file-service")
+```
+
+注意:更新版本时请先停止在正在跑或调试的工程,避免文件占用无法覆盖。
+
+## 3.工程打包与部署
+
+工程打包有多种方式,可通过Idea或Eclipse中Gradle命令界面build菜单下的"buildJar"任务,也可以打开CMD终端使用"gradle buildJar"命令方式实现。
+
+```cmd
+cd your_project_dir
+gradle buildJar
+```
+
+Jar包部署有多种方式,可通过Idea或Eclipse中Gradle命令界面build菜单下的"deployJar"任务,也可以打开CMD终端使用"gradle deployJar"命令方式实现。
+
+```cmd
+cd your_project_dir
+gradle deployJar
+```
+
+推荐开发工具自带的Gradle构建菜单进行工程的构建、清理及部署等任务。
+
+Idea Gradle工具菜单如下图:
+
+ ![idea-gradle](docs/images/idea-gradle.png)
+
+
+Eclipse Gradle工具菜单如下图:
+
+ ![idea-gradle](docs/images/eclipse-gradle.png)
+
+
+
+## 6.金蝶代码扫描
+右键项目工程目录,在右侧菜单中,点击“金蝶代码分析/金蝶代码扫描”功能,即可实现对该工程代码的静态代码扫描。
+
+ ![code1](docs/images/code1.png)
+ 
+ 扫描结构如下图:
+ 
+ ![code2](docs/images/code2.png)
+
+## 5.插件选项
+菜单操作路径:File->Settings->苍穹开发助手
+
+
+## 6.搜索工具窗
+菜单操作路径:View->Tool Windows->苍穹开发助手
+
+若当前为苍穹工程(工程根目录下有cosmic.properties文件),苍穹开发助手自行显示,停靠在右上角。
+
+提供文档检索功能。
+
+
+## 7.菜单-登录开发者门户
+未登录:苍穹开发助手(工具窗)-社区文章-可搜索“开发者社区”文档
+登录后:苍穹开发助手(工具窗)-社区文章-可搜索“开发者社区+开发者门户”文档
+
+## 8.常见问题
+8.1. 忘记ZK账号密码,可通过SQL在MC数据库实例中查询,参考如下SQL
+
+```sql
+select t.furl, t.fusername, t.fpassword from t_mc_zookeeper t ;
+```
+
+## 9.相关文档
+
+	1、苍穹开发者工具下载与安装
+	https://developer.kingdee.com/article/418778103486608384
+	
+	2、金蝶云·苍穹开发环境搭建
+	https://developer.kingdee.com/article/418816210550117376
+	
+	3、金蝶云·苍穹开发者助手插件安装
+	https://developer.kingdee.com/article/476393455359492608
+	
+	4、金蝶云·苍穹代码规范扫描介绍
+	https://developer.kingdee.com/article/476783839013202176
+	
+	5、IDEA启动命令行过长解决办法
+	https://vip.kingdee.com/article/357806944599232512
+	
+	6、如何使用苍穹虚拟机搭建二开环境
+	https://vip.kingdee.com/article/357950002276007424
+
+
+## 🤝 联系我们
+
+请使用云之家扫描以下二维码。
+
+### 苍穹开发者工具交流反馈群
+ ![qrcode](docs/images/cosmic-studio-qrcode.png)
+

+ 37 - 0
settings.gradle

@@ -0,0 +1,37 @@
+/**
+ * This is a kingdee cosmic template project that is automatically generated by the Kingdee cosmic development assistant plugin. 
+ * If there are any issues during the use process, you can provide feedback to the kingdee developer community website.
+ * Website: https://developer.kingdee.com/developer?productLineId=29
+ * Author: liebin.zheng
+ * Generate Date: 2025-11-21 10:24:13
+ */
+rootProject.name = System.getProperty('artifactId')
+
+include(
+		'nckd-cosmic-debug',
+		':nckd-base-common',
+		':nckd-base-helper',
+		
+		':nckd-fi',
+
+		':nckd-hr',
+
+		':nckd-pur',
+)
+
+// -------------- 引入苍穹调试工程模块 --------------
+//project(':nckd-cosmic-debug').projectDir = new File('nckd-cosmic-debug')
+
+// -------------- 引入公共基础模块 --------------
+project(':nckd-base-common').projectDir = new File('base/nckd-base-common')
+project(':nckd-base-helper').projectDir = new File('base/nckd-base-helper')
+
+// -------------- 引入fi云模块 --------------
+project(':nckd-fi').projectDir = new File('nckd-fi')
+
+// -------------- 引入hr云模块 --------------
+project(':nckd-hr').projectDir = new File('nckd-hr')
+
+// -------------- 引入pur云模块 --------------
+project(':nckd-pur').projectDir = new File('nckd-pur')
+