feat: 实现员工同步任务的WebSocket支持及合同名称锁定功能

- 为EmployeesSyncTask添加WebSocket客户端和服务端支持,实现实时任务进度反馈
- 新增合同名称锁定功能,防止误修改重要合同名称
- 优化SmbFileService的连接异常处理,提高稳定性
- 重构ContractFilesRebuildTasker的任务执行逻辑,改进错误处理
- 更新tasker_mapper.json注册EmployeesSyncTask
- 添加相关任务文档和验收报告

修复WebSocketClientSession的任务完成状态处理问题
改进UITools中任务执行的线程管理
优化DepartmentService的findByCode方法返回类型
This commit is contained in:
2025-11-20 16:26:34 +08:00
parent 02afa189f8
commit a784438e97
28 changed files with 983 additions and 329 deletions

View File

@@ -353,14 +353,6 @@ public class WebSocketClientService {
return online;
}
public void withSession(Consumer<WebSocketClientSession> sessionConsumer) {
WebSocketClientSession session = createSession();
try {
sessionConsumer.accept(session);
} finally {
// closeSession(session);vvvv
}
}
public void closeSession(WebSocketClientSession session) {
if (session != null) {

View File

@@ -1,22 +1,19 @@
package com.ecep.contract;
import com.ecep.contract.constant.WebSocketConstant;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import java.beans.PropertyDescriptor;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
import javafx.application.Platform;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import com.ecep.contract.constant.WebSocketConstant;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Getter;
/**
*
*/
@@ -27,6 +24,8 @@ public class WebSocketClientSession {
*/
@Getter
private final String sessionId = UUID.randomUUID().toString();
@Getter
private boolean done = false;
private WebSocketClientTasker tasker;
@@ -69,6 +68,8 @@ public class WebSocketClientSession {
handleAsStart(args);
} else if (type.equals("done")) {
handleAsDone(args);
done = true;
close();
} else {
tasker.updateMessage(java.util.logging.Level.INFO, "未知的消息类型: " + node.toString());
}
@@ -83,7 +84,6 @@ public class WebSocketClientSession {
private void handleAsDone(JsonNode args) {
tasker.updateMessage(java.util.logging.Level.INFO, "任务完成");
close();
}
private void handleAsProgress(JsonNode args) {

View File

@@ -5,20 +5,22 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import java.util.Locale;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
/**
* WebSocket客户端任务接口
* 定义了所有通过WebSocket与服务器通信的任务的通用方法
* 包括任务名称、更新消息、更新标题、更新进度等操作
*
* <p>
* 所有通过WebSocket与服务器通信的任务类都应实现此接口, 文档参考 .trace/rules/client_task_rules.md
*/
public interface WebSocketClientTasker {
/**s
/**
* s
* 获取任务名称
* 任务名称用于唯一标识任务, 服务器端会根据任务名称来调用对应的任务处理函数
*
*
* @return 任务名称
*/
String getTaskName();
@@ -26,7 +28,7 @@ public interface WebSocketClientTasker {
/**
* 更新任务执行过程中的消息
* 客户端可以通过此方法向用户展示任务执行过程中的重要信息或错误提示
*
*
* @param level 消息级别, 用于区分不同类型的消息, 如INFO, WARNING, SEVERE等
* @param message 消息内容, 可以是任意字符串, 用于展示给用户
*/
@@ -35,7 +37,7 @@ public interface WebSocketClientTasker {
/**
* 更新任务标题
* 客户端可以通过此方法向用户展示任务的当前执行状态或重要信息
*
*
* @param title 任务标题
*/
void updateTitle(String title);
@@ -43,7 +45,7 @@ public interface WebSocketClientTasker {
/**
* 更新任务进度
* 客户端可以通过此方法向用户展示任务的执行进度, 如文件上传进度、数据库操作进度等
*
*
* @param current 当前进度
* @param total 总进度
*/
@@ -55,12 +57,15 @@ public interface WebSocketClientTasker {
*/
default void cancelTask() {
// 默认实现为空,由具体任务重写
// 需要获取到 session
// 发送 cancel 指令
// 关闭 session.close()
}
/**
* 调用远程WebSocket任务
* 客户端可以通过此方法向服务器提交任务, 并等待服务器返回任务执行结果
*
*
* @param holder 消息持有者,用于记录任务执行过程中的消息
* @param locale 语言环境
* @param args 任务参数
@@ -76,22 +81,31 @@ public interface WebSocketClientTasker {
return null;
}
webSocketService.withSession(session -> {
try {
session.submitTask(this, locale, args);
holder.info("已提交任务到服务器: " + getTaskName());
} catch (JsonProcessingException e) {
String errorMsg = "任务提交失败: " + e.getMessage();
holder.warn(errorMsg);
throw new RuntimeException("任务提交失败: " + e.getMessage(), e);
}
});
WebSocketClientSession session = webSocketService.createSession();
try {
session.submitTask(this, locale, args);
holder.info("已提交任务到服务器: " + getTaskName());
} catch (JsonProcessingException e) {
String errorMsg = "任务提交失败: " + e.getMessage();
holder.warn(errorMsg);
throw new RuntimeException("任务提交失败: " + e.getMessage(), e);
}
// while (!session.isDone()) {
// // 使用TimeUnit
// try {
// TimeUnit.SECONDS.sleep(1);
// } catch (InterruptedException e) {
// Thread.currentThread().interrupt();
// }
// }
return null;
}
/**
* 异步调用远程WebSocket任务
*
*
* @param holder 消息持有者,用于记录任务执行过程中的消息
* @param locale 语言环境
* @param args 任务参数
@@ -113,7 +127,7 @@ public interface WebSocketClientTasker {
/**
* 生成唯一的任务ID
*
*
* @return 唯一的任务ID
*/
default String generateTaskId() {

View File

@@ -2,6 +2,7 @@ package com.ecep.contract.controller.contract;
import java.io.File;
import javafx.scene.control.*;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@@ -26,13 +27,6 @@ import com.ecep.contract.vo.ContractVo;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.scene.control.Button;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.stage.Window;
import javafx.stage.WindowEvent;
@@ -44,6 +38,8 @@ import javafx.stage.WindowEvent;
public class ContractWindowController
extends AbstEntityController<ContractVo, ContractViewModel> {
public static void show(ContractVo contract, Window owner) {
show(ContractViewModel.from(contract), owner);
}
@@ -69,6 +65,7 @@ public class ContractWindowController
public Button openRelativeCompanyVendorBtn;
public TextField nameField;
public CheckBox contractNameLockedCk;
public TextField guidField;
public TextField codeField;
public TextField parentCodeField;

View File

@@ -85,7 +85,9 @@ public class ContractTabSkinBase extends AbstContractBasedTabSkin {
controller.payWayField.textProperty().bind(viewModel.getPayWay().map(ContractPayWay::getText));
controller.nameField.textProperty().bind(viewModel.getName());
controller.nameField.textProperty().bindBidirectional(viewModel.getName());
controller.nameField.editableProperty().bind(viewModel.getNameLocked());
controller.contractNameLockedCk.selectedProperty().bindBidirectional(viewModel.getNameLocked());
controller.codeField.textProperty().bind(viewModel.getCode());
controller.parentCodeField.textProperty().bindBidirectional(viewModel.getParentCode());
@@ -351,17 +353,20 @@ public class ContractTabSkinBase extends AbstContractBasedTabSkin {
}
if (initialDirectory == null) {
if (entity.getPayWay() == ContractPayWay.RECEIVE) {
// 根据项目设置初始目录
ProjectVo project = getProjectService().findById(entity.getProject());
if (project != null) {
// 根据项目销售方式设置初始目录
ProjectSaleTypeVo saleType = getSaleTypeService().findById(project.getSaleTypeId());
if (saleType != null) {
File dir = new File(saleType.getPath());
if (saleType.isStoreByYear()) {
dir = new File(dir, "20" + project.getCodeYear());
Integer projectId = entity.getProject();
if (projectId != null) {
// 根据项目设置初始目录
ProjectVo project = getProjectService().findById(projectId);
if (project != null) {
// 根据项目销售方式设置初始目录
ProjectSaleTypeVo saleType = getSaleTypeService().findById(project.getSaleTypeId());
if (saleType != null) {
File dir = new File(saleType.getPath());
if (saleType.isStoreByYear()) {
dir = new File(dir, "20" + project.getCodeYear());
}
initialDirectory = dir;
}
initialDirectory = dir;
}
}
} else if (entity.getPayWay() == ContractPayWay.PAY) {

View File

@@ -18,12 +18,6 @@ public class ContractRepairAllTasker extends Tasker<Object> implements WebSocket
super.updateProgress(current, total);
}
@Override
public void updateTitle(String title) {
// 使用Tasker的updateTitle方法更新标题
super.updateTitle(title);
}
@Override
public String getTaskName() {
return "ContractRepairAllTask";
@@ -34,6 +28,7 @@ public class ContractRepairAllTasker extends Tasker<Object> implements WebSocket
logger.info("开始执行合同修复任务");
updateTitle("合同数据修复");
Object result = callRemoteTask(holder, getLocale());
logger.info("合同修复任务执行完成");
return result;
}

View File

@@ -1,13 +1,37 @@
package com.ecep.contract.task;
import com.ecep.contract.MessageHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class EmployeesSyncTask extends Tasker<Object> {
import com.ecep.contract.MessageHolder;
import com.ecep.contract.WebSocketClientTasker;
/**
* 员工同步任务客户端实现
* 用于通过WebSocket与服务器通信执行用友U8系统员工信息同步
*/
public class EmployeesSyncTask extends Tasker<Object> implements WebSocketClientTasker {
private static final Logger logger = LoggerFactory.getLogger(EmployeesSyncTask.class);
@Override
public String getTaskName() {
return "EmployeesSyncTask";
}
@Override
public void updateProgress(long current, long total) {
super.updateProgress(current, total);
}
@Override
protected Object execute(MessageHolder holder) throws Exception {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'execute'");
}
// 设置任务标题
updateTitle("用友U8系统-同步员工信息");
// 更新任务消息
updateMessage("开始执行员工信息同步...");
// 调用远程WebSocket任务
return callRemoteTask(holder, getLocale());
}
}

View File

@@ -4,14 +4,11 @@ import java.util.HashMap;
import java.util.Locale;
import java.util.logging.Level;
import com.ecep.contract.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import com.ecep.contract.Desktop;
import com.ecep.contract.Message;
import com.ecep.contract.MessageHolder;
import com.ecep.contract.SpringApp;
import com.ecep.contract.service.CompanyService;
import com.ecep.contract.service.EmployeeService;
import com.ecep.contract.service.SysConfService;
@@ -63,6 +60,22 @@ public abstract class Tasker<T> extends Task<T> {
return currentUser;
}
@Override
public void run() {
if (this instanceof WebSocketClientTasker) {
this.getState();
}
super.run();
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
if (this instanceof WebSocketClientTasker) {
((WebSocketClientTasker) this).cancelTask();
}
return super.cancel(mayInterruptIfRunning);
}
@Override
protected T call() throws Exception {
MessageHolderImpl holder = new MessageHolderImpl();

View File

@@ -4,6 +4,8 @@ import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -221,15 +223,19 @@ public class UITools {
if (task instanceof Tasker<?> tasker) {
// 提交任务
Desktop.instance.getExecutorService().submit(tasker);
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(tasker);
}
}
if (init != null) {
init.accept(consumer::test);
}
dialog.showAndWait();
if (task.isRunning()) {
if (task.getProgress() < 1) {
task.cancel();
}
// if (task.isRunning()) {
// }
}
private static String printStackTrace(Throwable e) {

View File

@@ -26,6 +26,10 @@ public class ContractViewModel extends IdentityViewModel<ContractVo> {
private SimpleStringProperty code = new SimpleStringProperty();
private SimpleStringProperty name = new SimpleStringProperty();
/**
* 名称是否锁定
*/
private SimpleBooleanProperty nameLocked = new SimpleBooleanProperty();
private SimpleStringProperty state = new SimpleStringProperty();
/**
* 分组
@@ -138,6 +142,7 @@ public class ContractViewModel extends IdentityViewModel<ContractVo> {
getPayWay().set(c.getPayWay());
getCode().set(c.getCode());
getName().set(c.getName());
getNameLocked().set(c.isNameLocked());
getState().set(c.getState());
getGroup().set(c.getGroupId());
@@ -198,6 +203,10 @@ public class ContractViewModel extends IdentityViewModel<ContractVo> {
contract.setName(name.get());
modified = true;
}
if (!Objects.equals(nameLocked.get(), contract.isNameLocked())) {
contract.setNameLocked(nameLocked.get());
modified = true;
}
if (!Objects.equals(state.get(), contract.getState())) {
contract.setState(state.get());
modified = true;

View File

@@ -1,36 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.DatePicker?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.ToolBar?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<?import org.controlsfx.glyphfont.Glyph?>
<BorderPane fx:id="root" maxHeight="900" maxWidth="1024" minHeight="300" minWidth="200" prefHeight="561.0"
prefWidth="800.0" xmlns="http://javafx.com/javafx/22" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.ecep.contract.controller.contract.ContractWindowController">
<BorderPane fx:id="root" maxHeight="900" maxWidth="1024" minHeight="300" minWidth="200" prefHeight="561.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/22" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.ecep.contract.controller.contract.ContractWindowController">
<top>
<ToolBar prefHeight="40.0" prefWidth="800.0" BorderPane.alignment="CENTER">
<items>
<Button onAction="#onContractOpenInExplorerAction" text="打开目录(_D)"/>
<Button onAction="#onSyncContractAction" text="同步(_R)"/>
<Button onAction="#onContractOpenInExplorerAction" text="打开目录(_D)" />
<Button onAction="#onSyncContractAction" text="同步(_R)" />
<Button fx:id="saveBtn" disable="true" text="保存(_S)">
<graphic>
<Glyph fontFamily="FontAwesome" icon="SAVE"/>
<Glyph fontFamily="FontAwesome" icon="SAVE" />
</graphic>
</Button>
<Button fx:id="verifyBtn" onAction="#onContractVerifyAction" text="合规(_V)">
<graphic>
<Glyph fontFamily="FontAwesome" icon="CHECK"/>
<Glyph fontFamily="FontAwesome" icon="CHECK" />
</graphic>
<tooltip>
<Tooltip text="按照预定规则检测合同是否符合合规要求"/>
<Tooltip text="按照预定规则检测合同是否符合合规要求" />
</tooltip>
</Button>
</items>
</ToolBar>
</top>
<center>
<TabPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minWidth="150.0"
tabClosingPolicy="UNAVAILABLE" tabMaxWidth="100.0" tabMinWidth="40.0">
<TabPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minWidth="150.0" tabClosingPolicy="UNAVAILABLE" tabMaxWidth="100.0" tabMinWidth="40.0">
<tabs>
<Tab fx:id="baseInfoTab" text="基本信息">
<content>
@@ -40,262 +54,190 @@
<children>
<GridPane VBox.vgrow="NEVER">
<columnConstraints>
<ColumnConstraints fillWidth="false" halignment="CENTER" hgrow="NEVER"
maxWidth="200.0" minWidth="80.0" prefWidth="120.0"/>
<ColumnConstraints hgrow="ALWAYS" maxWidth="1.7976931348623157E308"
minWidth="100.0" prefWidth="180.0"/>
<ColumnConstraints fillWidth="false" halignment="CENTER" hgrow="NEVER"
maxWidth="200.0" minWidth="80.0" prefWidth="120.0"/>
<ColumnConstraints hgrow="ALWAYS" maxWidth="1.7976931348623157E308"
minWidth="100.0" prefWidth="180.0"/>
<ColumnConstraints fillWidth="false" halignment="CENTER" hgrow="NEVER" maxWidth="200.0" minWidth="80.0" prefWidth="120.0" />
<ColumnConstraints hgrow="ALWAYS" maxWidth="1.7976931348623157E308" minWidth="100.0" prefWidth="180.0" />
<ColumnConstraints fillWidth="false" halignment="CENTER" hgrow="NEVER" maxWidth="200.0" minWidth="80.0" prefWidth="120.0" />
<ColumnConstraints hgrow="ALWAYS" maxWidth="1.7976931348623157E308" minWidth="100.0" prefWidth="180.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0"
vgrow="NEVER"/>
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0"
vgrow="NEVER"/>
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0"
vgrow="NEVER"/>
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0"
vgrow="NEVER"/>
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0"
vgrow="NEVER"/>
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0"
vgrow="NEVER"/>
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0"
vgrow="NEVER"/>
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0"
vgrow="NEVER"/>
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0"
vgrow="NEVER"/>
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0"
vgrow="NEVER"/>
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0"
vgrow="NEVER"/>
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0"
vgrow="NEVER"/>
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0"
vgrow="NEVER"/>
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0"
vgrow="NEVER"/>
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0"
vgrow="NEVER"/>
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0"
vgrow="NEVER"/>
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0"
vgrow="NEVER"/>
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0"
vgrow="NEVER"/>
<RowConstraints fillHeight="false" maxHeight="1.7976931348623157E308"
minHeight="80.0" prefHeight="80.0" vgrow="NEVER"/>
<RowConstraints fillHeight="false" maxHeight="1.7976931348623157E308"
minHeight="30.0" prefHeight="42.0" vgrow="NEVER"/>
<RowConstraints fillHeight="false" maxHeight="1.7976931348623157E308"
minHeight="30.0" prefHeight="42.0" vgrow="NEVER"/>
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0" vgrow="NEVER" />
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0" vgrow="NEVER" />
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0" vgrow="NEVER" />
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0" vgrow="NEVER" />
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0" vgrow="NEVER" />
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0" vgrow="NEVER" />
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0" vgrow="NEVER" />
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0" vgrow="NEVER" />
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0" vgrow="NEVER" />
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0" vgrow="NEVER" />
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0" vgrow="NEVER" />
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0" vgrow="NEVER" />
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0" vgrow="NEVER" />
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0" vgrow="NEVER" />
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0" vgrow="NEVER" />
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0" vgrow="NEVER" />
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0" vgrow="NEVER" />
<RowConstraints fillHeight="false" minHeight="30.0" prefHeight="30.0" vgrow="NEVER" />
<RowConstraints fillHeight="false" maxHeight="1.7976931348623157E308" minHeight="80.0" prefHeight="80.0" vgrow="NEVER" />
<RowConstraints fillHeight="false" maxHeight="1.7976931348623157E308" minHeight="30.0" prefHeight="42.0" vgrow="NEVER" />
<RowConstraints fillHeight="false" maxHeight="1.7976931348623157E308" minHeight="30.0" prefHeight="42.0" vgrow="NEVER" />
</rowConstraints>
<children>
<Label text="合同名称 *"/>
<Label text="合同编码 *" GridPane.rowIndex="2"/>
<Label text="状态 *" GridPane.columnIndex="2" GridPane.rowIndex="4"/>
<TextField fx:id="codeField" GridPane.columnIndex="1"
GridPane.rowIndex="2"/>
<TextField fx:id="stateField" GridPane.columnIndex="3"
GridPane.rowIndex="4"/>
<Label text="合同名称 *" />
<Label text="合同编码 *" GridPane.rowIndex="2" />
<Label text="状态 *" GridPane.columnIndex="2" GridPane.rowIndex="4" />
<TextField fx:id="codeField" GridPane.columnIndex="1" GridPane.rowIndex="2" />
<TextField fx:id="stateField" GridPane.columnIndex="3" GridPane.rowIndex="4" />
<HBox GridPane.columnIndex="1" GridPane.columnSpan="3">
<children>
<TextField fx:id="nameField" HBox.hgrow="ALWAYS"/>
<Button fx:id="contractRenameBtn" mnemonicParsing="false"
text="合同更名"/>
<TextField fx:id="nameField" HBox.hgrow="ALWAYS" />
<CheckBox fx:id="contractNameLockedCk" mnemonicParsing="false">
<HBox.margin>
<Insets left="4.0" right="4.0" top="4.0" />
</HBox.margin>
<tooltip>
<Tooltip text="锁定合同名称" />
</tooltip>
</CheckBox>
<Button fx:id="contractRenameBtn" mnemonicParsing="false" text="合同更名" />
</children>
</HBox>
<Label layoutX="20.0" layoutY="28.0" text="GUID *"
GridPane.columnIndex="2" GridPane.rowIndex="17"/>
<Label layoutX="20.0" layoutY="58.0" text="分组 *"
GridPane.columnIndex="2" GridPane.rowIndex="3"/>
<Label layoutX="20.0" layoutY="28.0" text="备注"
GridPane.rowIndex="18"/>
<TextField fx:id="groupField" layoutX="202.0" layoutY="114.0"
GridPane.columnIndex="3" GridPane.rowIndex="3"/>
<TextField fx:id="guidField" GridPane.columnIndex="3"
GridPane.rowIndex="17"/>
<Label layoutX="56.0" layoutY="118.0" text="类型 *"
GridPane.columnIndex="2" GridPane.rowIndex="1"/>
<TextField fx:id="typeField" GridPane.columnIndex="3"
GridPane.rowIndex="1"/>
<TextField fx:id="kindField" GridPane.columnIndex="3"
GridPane.rowIndex="2"/>
<Label text="性质 *" GridPane.columnIndex="2" GridPane.rowIndex="2"/>
<Label text="合同起止日期 *" GridPane.rowIndex="8"/>
<Label text="存储目录" GridPane.rowIndex="15"/>
<TextField fx:id="pathField" GridPane.columnIndex="1"
GridPane.columnSpan="3" GridPane.rowIndex="15">
<Label layoutX="20.0" layoutY="28.0" text="GUID *" GridPane.columnIndex="2" GridPane.rowIndex="17" />
<Label layoutX="20.0" layoutY="58.0" text="分组 *" GridPane.columnIndex="2" GridPane.rowIndex="3" />
<Label layoutX="20.0" layoutY="28.0" text="备注" GridPane.rowIndex="18" />
<TextField fx:id="groupField" layoutX="202.0" layoutY="114.0" GridPane.columnIndex="3" GridPane.rowIndex="3" />
<TextField fx:id="guidField" GridPane.columnIndex="3" GridPane.rowIndex="17" />
<Label layoutX="56.0" layoutY="118.0" text="类型 *" GridPane.columnIndex="2" GridPane.rowIndex="1" />
<TextField fx:id="typeField" GridPane.columnIndex="3" GridPane.rowIndex="1" />
<TextField fx:id="kindField" GridPane.columnIndex="3" GridPane.rowIndex="2" />
<Label text="性质 *" GridPane.columnIndex="2" GridPane.rowIndex="2" />
<Label text="合同起止日期 *" GridPane.rowIndex="8" />
<Label text="存储目录" GridPane.rowIndex="15" />
<TextField fx:id="pathField" GridPane.columnIndex="1" GridPane.columnSpan="3" GridPane.rowIndex="15">
<GridPane.margin>
<Insets/>
<Insets />
</GridPane.margin>
</TextField>
<Label text="创建日期" GridPane.rowIndex="17"/>
<TextField fx:id="createdField" GridPane.columnIndex="1"
GridPane.rowIndex="17"/>
<Label text="创建日期" GridPane.rowIndex="17" />
<TextField fx:id="createdField" GridPane.columnIndex="1" GridPane.rowIndex="17" />
<HBox GridPane.columnIndex="1" GridPane.rowIndex="8">
<children>
<DatePicker fx:id="startDateField"
maxWidth="1.7976931348623157E308"
promptText="启时日期" HBox.hgrow="ALWAYS">
<DatePicker fx:id="startDateField" maxWidth="1.7976931348623157E308" promptText="启时日期" HBox.hgrow="ALWAYS">
<HBox.margin>
<Insets right="6.0"/>
<Insets right="6.0" />
</HBox.margin>
</DatePicker>
<DatePicker fx:id="endDateField"
maxWidth="1.7976931348623157E308"
promptText="截止日期" HBox.hgrow="ALWAYS"/>
<DatePicker fx:id="endDateField" maxWidth="1.7976931348623157E308" promptText="截止日期" HBox.hgrow="ALWAYS" />
</children>
</HBox>
<TextArea fx:id="descriptionField" GridPane.columnIndex="1"
GridPane.columnSpan="3" GridPane.rowIndex="18"/>
<Label text="提交人 *" GridPane.columnIndex="2" GridPane.rowIndex="8"/>
<TextArea fx:id="descriptionField" GridPane.columnIndex="1" GridPane.columnSpan="3" GridPane.rowIndex="18" />
<Label text="提交人 *" GridPane.columnIndex="2" GridPane.rowIndex="8" />
<HBox GridPane.columnIndex="3" GridPane.rowIndex="8">
<children>
<TextField fx:id="setupPersonField" HBox.hgrow="SOMETIMES"/>
<DatePicker fx:id="setupDateField" HBox.hgrow="SOMETIMES"/>
<TextField fx:id="setupPersonField" HBox.hgrow="SOMETIMES" />
<DatePicker fx:id="setupDateField" HBox.hgrow="SOMETIMES" />
</children>
</HBox>
<Label text="签订日期 *" GridPane.rowIndex="9"/>
<DatePicker fx:id="orderDateField" maxWidth="1.7976931348623157E308"
GridPane.columnIndex="1" GridPane.rowIndex="9"/>
<Label text="生效人 *" GridPane.columnIndex="2" GridPane.rowIndex="9"/>
<Label text="修改人 *" GridPane.columnIndex="2" GridPane.rowIndex="10"/>
<Label text="签订日期 *" GridPane.rowIndex="9" />
<DatePicker fx:id="orderDateField" maxWidth="1.7976931348623157E308" GridPane.columnIndex="1" GridPane.rowIndex="9" />
<Label text="生效人 *" GridPane.columnIndex="2" GridPane.rowIndex="9" />
<Label text="修改人 *" GridPane.columnIndex="2" GridPane.rowIndex="10" />
<HBox GridPane.columnIndex="3" GridPane.rowIndex="9">
<children>
<TextField fx:id="inurePersonField" HBox.hgrow="SOMETIMES"/>
<DatePicker fx:id="inureDateField" HBox.hgrow="SOMETIMES"/>
<TextField fx:id="inurePersonField" HBox.hgrow="SOMETIMES" />
<DatePicker fx:id="inureDateField" HBox.hgrow="SOMETIMES" />
</children>
</HBox>
<HBox GridPane.columnIndex="3" GridPane.rowIndex="10">
<children>
<TextField fx:id="varyPersonField" HBox.hgrow="SOMETIMES"/>
<DatePicker fx:id="varyDateField" HBox.hgrow="SOMETIMES"/>
<TextField fx:id="varyPersonField" HBox.hgrow="SOMETIMES" />
<DatePicker fx:id="varyDateField" HBox.hgrow="SOMETIMES" />
</children>
</HBox>
<TextField fx:id="parentCodeField" GridPane.columnIndex="1"
GridPane.rowIndex="3"/>
<Label text="主合同编码" GridPane.rowIndex="3"/>
<Label fx:id="versionLabel" text="\@Version" GridPane.rowIndex="19"/>
<HBox alignment="CENTER_RIGHT" spacing="5.0" GridPane.columnIndex="1"
GridPane.columnSpan="3" GridPane.rowIndex="16">
<TextField fx:id="parentCodeField" GridPane.columnIndex="1" GridPane.rowIndex="3" />
<Label text="主合同编码" GridPane.rowIndex="3" />
<Label fx:id="versionLabel" text="\@Version" GridPane.rowIndex="19" />
<HBox alignment="CENTER_RIGHT" spacing="5.0" GridPane.columnIndex="1" GridPane.columnSpan="3" GridPane.rowIndex="16">
<children>
<Button fx:id="contractPathCreateBtn" mnemonicParsing="false"
text="创建目录"/>
<Button fx:id="contractPathChangeBtn" mnemonicParsing="false"
text="变更目录"/>
<Button fx:id="contractPathAsNameBtn" mnemonicParsing="false"
text="与合同名称同名"/>
<Button fx:id="contractPathAsCodeBtn" mnemonicParsing="false"
text="与合同号同名"/>
<Button fx:id="contractPathCreateBtn" mnemonicParsing="false" text="创建目录" />
<Button fx:id="contractPathChangeBtn" mnemonicParsing="false" text="变更目录" />
<Button fx:id="contractPathAsNameBtn" mnemonicParsing="false" text="与合同名称同名" />
<Button fx:id="contractPathAsCodeBtn" mnemonicParsing="false" text="与合同号同名" />
</children>
</HBox>
<HBox alignment="CENTER_RIGHT" spacing="5.0" GridPane.columnIndex="1"
GridPane.rowIndex="4">
<HBox alignment="CENTER_RIGHT" spacing="5.0" GridPane.columnIndex="1" GridPane.rowIndex="4">
<children>
<Button fx:id="openMainContractBtn" mnemonicParsing="false"
text="打开主合同"/>
<Button fx:id="calcMainContractNoBtn" mnemonicParsing="false"
text="计算主合同编号"/>
<Button fx:id="openMainContractBtn" mnemonicParsing="false" text="打开主合同" />
<Button fx:id="calcMainContractNoBtn" mnemonicParsing="false" text="计算主合同编号" />
</children>
</HBox>
<Label text="对方单位 *" GridPane.rowIndex="5"/>
<HBox spacing="5.0" GridPane.columnIndex="1" GridPane.columnSpan="3"
GridPane.rowIndex="5">
<Label text="对方单位 *" GridPane.rowIndex="5" />
<HBox spacing="5.0" GridPane.columnIndex="1" GridPane.columnSpan="3" GridPane.rowIndex="5">
<children>
<TextField fx:id="companyField" HBox.hgrow="SOMETIMES"/>
<Button mnemonicParsing="false"
onAction="#onContractOpenRelativeCompanyAction"
text="详情">
<TextField fx:id="companyField" HBox.hgrow="SOMETIMES" />
<Button mnemonicParsing="false" onAction="#onContractOpenRelativeCompanyAction" text="详情">
<HBox.margin>
<Insets/>
<Insets />
</HBox.margin>
</Button>
<Button fx:id="openRelativeCompanyCustomerBtn"
mnemonicParsing="false" text="客户">
<Button fx:id="openRelativeCompanyCustomerBtn" mnemonicParsing="false" text="客户">
<HBox.margin>
<Insets/>
<Insets />
</HBox.margin>
</Button>
<Button fx:id="openRelativeCompanyVendorBtn"
mnemonicParsing="false" text="供应商"/>
<Button fx:id="openRelativeCompanyVendorBtn" mnemonicParsing="false" text="供应商" />
</children>
</HBox>
<Label text="业务员 *" GridPane.rowIndex="11"/>
<Label text="业务员 *" GridPane.rowIndex="11" />
<VBox GridPane.columnIndex="1" GridPane.rowIndex="11">
<children>
<TextField fx:id="employeeField"/>
<TextField fx:id="employeeField" />
</children>
<GridPane.margin>
<Insets bottom="5.0" top="5.0"/>
<Insets bottom="5.0" top="5.0" />
</GridPane.margin>
</VBox>
<Label text="经办人" GridPane.columnIndex="2" GridPane.rowIndex="11"/>
<Label text="经办人" GridPane.columnIndex="2" GridPane.rowIndex="11" />
<VBox GridPane.columnIndex="3" GridPane.rowIndex="11">
<children>
<TextField fx:id="handlerField"/>
<TextField fx:id="handlerField" />
</children>
<GridPane.margin>
<Insets bottom="5.0" top="5.0"/>
<Insets bottom="5.0" top="5.0" />
</GridPane.margin>
</VBox>
<Label text="归属项目" GridPane.rowIndex="1"/>
<Label text="归属项目" GridPane.rowIndex="1" />
<HBox GridPane.columnIndex="1" GridPane.rowIndex="1">
<children>
<TextField fx:id="projectField" prefWidth="190.0"
HBox.hgrow="ALWAYS"/>
<Button fx:id="linkContractProjectBtn" mnemonicParsing="false"
text="关联" textOverrun="CLIP">
<TextField fx:id="projectField" prefWidth="190.0" HBox.hgrow="ALWAYS" />
<Button fx:id="linkContractProjectBtn" mnemonicParsing="false" text="关联" textOverrun="CLIP">
</Button>
</children>
</HBox>
<Label text="1. (*) 字段数据同步自用友U8系统同步时将被覆盖"
textFill="#888888" wrapText="true" GridPane.columnIndex="1"
GridPane.columnSpan="3" GridPane.rowIndex="20"/>
<Label text="合同金额" GridPane.rowIndex="6"/>
<TextField fx:id="amountField" alignment="CENTER_RIGHT"
GridPane.columnIndex="1" GridPane.rowIndex="6"/>
<Label text="合同总数量 *" GridPane.rowIndex="12"/>
<Label text="合同总金额 *" GridPane.rowIndex="13"/>
<Label text="不含税总金额 *" GridPane.rowIndex="14"/>
<Label text="执行总数量 *" GridPane.columnIndex="2"
GridPane.rowIndex="12"/>
<Label text="执行总金额 *" GridPane.columnIndex="2"
GridPane.rowIndex="13"/>
<Label text="不含税总金额 *" GridPane.columnIndex="2"
GridPane.rowIndex="14"/>
<TextField fx:id="totalQuantityField" alignment="CENTER_RIGHT"
GridPane.columnIndex="1" GridPane.halignment="RIGHT"
GridPane.rowIndex="12"/>
<TextField fx:id="totalAmountField" alignment="CENTER_RIGHT"
GridPane.columnIndex="1" GridPane.halignment="RIGHT"
GridPane.rowIndex="13"/>
<TextField fx:id="totalUnTaxAmountField" alignment="CENTER_RIGHT"
GridPane.columnIndex="1" GridPane.halignment="RIGHT"
GridPane.rowIndex="14"/>
<TextField fx:id="execQuantityField" alignment="CENTER_RIGHT"
GridPane.columnIndex="3" GridPane.halignment="RIGHT"
GridPane.rowIndex="12"/>
<TextField fx:id="execAmountField" alignment="CENTER_RIGHT"
GridPane.columnIndex="3" GridPane.halignment="RIGHT"
GridPane.rowIndex="13"/>
<TextField fx:id="execUnTaxAmountField" alignment="CENTER_RIGHT"
GridPane.columnIndex="3" GridPane.halignment="RIGHT"
GridPane.rowIndex="14"/>
<Label text="付款方向 *" GridPane.columnIndex="2"
GridPane.rowIndex="6"/>
<TextField fx:id="payWayField" GridPane.columnIndex="3"
GridPane.rowIndex="6"/>
<Label text="1. (*) 字段数据同步自用友U8系统同步时将被覆盖" textFill="#888888" wrapText="true" GridPane.columnIndex="1" GridPane.columnSpan="3" GridPane.rowIndex="20" />
<Label text="合同金额" GridPane.rowIndex="6" />
<TextField fx:id="amountField" alignment="CENTER_RIGHT" GridPane.columnIndex="1" GridPane.rowIndex="6" />
<Label text="合同总数量 *" GridPane.rowIndex="12" />
<Label text="合同总金额 *" GridPane.rowIndex="13" />
<Label text="不含税总金额 *" GridPane.rowIndex="14" />
<Label text="执行总数量 *" GridPane.columnIndex="2" GridPane.rowIndex="12" />
<Label text="执行总金额 *" GridPane.columnIndex="2" GridPane.rowIndex="13" />
<Label text="不含税总金额 *" GridPane.columnIndex="2" GridPane.rowIndex="14" />
<TextField fx:id="totalQuantityField" alignment="CENTER_RIGHT" GridPane.columnIndex="1" GridPane.halignment="RIGHT" GridPane.rowIndex="12" />
<TextField fx:id="totalAmountField" alignment="CENTER_RIGHT" GridPane.columnIndex="1" GridPane.halignment="RIGHT" GridPane.rowIndex="13" />
<TextField fx:id="totalUnTaxAmountField" alignment="CENTER_RIGHT" GridPane.columnIndex="1" GridPane.halignment="RIGHT" GridPane.rowIndex="14" />
<TextField fx:id="execQuantityField" alignment="CENTER_RIGHT" GridPane.columnIndex="3" GridPane.halignment="RIGHT" GridPane.rowIndex="12" />
<TextField fx:id="execAmountField" alignment="CENTER_RIGHT" GridPane.columnIndex="3" GridPane.halignment="RIGHT" GridPane.rowIndex="13" />
<TextField fx:id="execUnTaxAmountField" alignment="CENTER_RIGHT" GridPane.columnIndex="3" GridPane.halignment="RIGHT" GridPane.rowIndex="14" />
<Label text="付款方向 *" GridPane.columnIndex="2" GridPane.rowIndex="6" />
<TextField fx:id="payWayField" GridPane.columnIndex="3" GridPane.rowIndex="6" />
</children>
<VBox.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0"/>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</VBox.margin>
</GridPane>
<Pane prefHeight="50.0" prefWidth="200.0"/>
<Pane prefHeight="50.0" prefWidth="200.0" />
</children>
</VBox>
</content>
@@ -320,14 +262,13 @@
<bottom>
<HBox id="HBox" alignment="CENTER_LEFT" prefWidth="800.0" spacing="5.0">
<children>
<Label fx:id="leftStatusLabel" maxHeight="1.7976931348623157E308" text="Left status"
HBox.hgrow="ALWAYS">
<Label fx:id="leftStatusLabel" maxHeight="1.7976931348623157E308" text="Left status" HBox.hgrow="ALWAYS">
</Label>
<Pane HBox.hgrow="ALWAYS"/>
<Label fx:id="rightStatusLabel" text="Right status" HBox.hgrow="NEVER"/>
<Pane HBox.hgrow="ALWAYS" />
<Label fx:id="rightStatusLabel" text="Right status" HBox.hgrow="NEVER" />
</children>
<padding>
<Insets bottom="3.0" left="3.0" right="3.0" top="3.0"/>
<Insets bottom="3.0" left="3.0" right="3.0" top="3.0" />
</padding>
</HBox>
</bottom>