From ab7c794dcb12bf86dd87929fd30167dd729e2c87 Mon Sep 17 00:00:00 2001
From: Harry Yang <i.take.today@gmail.com>
Date: Fri, 23 Dec 2022 10:59:25 +0800
Subject: [PATCH] =?UTF-8?q?=E6=B5=81=E7=A8=8B=E5=AF=B9=E6=8E=A5=EF=BC=8C?=
 =?UTF-8?q?=E6=B5=81=E7=A8=8B=E9=99=84=E4=BB=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../controller/backend/ProcessController.java |  92 ++-
 .../work/model/process/ProcessAttachment.java |  15 +
 .../work/model/process/ProjectProcess.java    |   3 +
 .../work/model/process/SealTypeArray.java     |   8 +
 .../process/form/SaleContractProcessForm.java |   3 +-
 .../work/service/ProjectProcessService.java   |  64 ++
 src/main/resources/sql/2.0.sql                |   3 +-
 .../admin/business/process-completed.ftl      |   4 +-
 .../admin/business/process-detail.ftl         | 648 ++++++++++++++++++
 .../templates/admin/business/process-new.ftl  |   7 +-
 .../admin/business/process-review.ftl         |   4 +-
 .../service/ProjectProcessServiceTests.java   |  27 +
 12 files changed, 856 insertions(+), 22 deletions(-)
 create mode 100644 src/main/java/cn/palmte/work/model/process/ProcessAttachment.java
 create mode 100644 src/main/java/cn/palmte/work/service/ProjectProcessService.java
 create mode 100644 src/main/resources/templates/admin/business/process-detail.ftl
 create mode 100644 src/test/java/cn/palmte/work/service/ProjectProcessServiceTests.java

diff --git a/src/main/java/cn/palmte/work/controller/backend/ProcessController.java b/src/main/java/cn/palmte/work/controller/backend/ProcessController.java
index 519aa8e..9eaed44 100644
--- a/src/main/java/cn/palmte/work/controller/backend/ProcessController.java
+++ b/src/main/java/cn/palmte/work/controller/backend/ProcessController.java
@@ -1,6 +1,7 @@
 package cn.palmte.work.controller.backend;
 
 import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
 
 import org.springframework.beans.BeanUtils;
 import org.springframework.data.domain.Example;
@@ -36,6 +37,7 @@ import java.util.Map;
 import java.util.stream.Collectors;
 
 import javax.persistence.EntityManager;
+import javax.persistence.TypedQuery;
 import javax.validation.Valid;
 import javax.validation.constraints.NotNull;
 
@@ -50,6 +52,7 @@ import cn.palmte.work.model.enums.ProcessStatus;
 import cn.palmte.work.model.enums.ProcessType;
 import cn.palmte.work.model.enums.ProjectType;
 import cn.palmte.work.model.enums.SealType;
+import cn.palmte.work.model.process.ProcessAttachment;
 import cn.palmte.work.model.process.ProcurementContract;
 import cn.palmte.work.model.process.ProjectProcess;
 import cn.palmte.work.model.process.ProjectProcessRepository;
@@ -59,6 +62,8 @@ import cn.palmte.work.model.process.form.ProcessQueryForm;
 import cn.palmte.work.model.process.form.SaleContractDetailForm;
 import cn.palmte.work.model.process.form.SaleContractProcessForm;
 import cn.palmte.work.service.ProjectBudgetService;
+import cn.palmte.work.service.ProjectInstanceService;
+import cn.palmte.work.service.ProjectProcessService;
 import cn.palmte.work.utils.InterfaceUtil;
 import lombok.Builder;
 import lombok.Data;
@@ -82,6 +87,8 @@ public class ProcessController {
   private final EntityManager entityManager;
 
   private final ProjectProcessRepository repository;
+  private final ProjectInstanceService projectInstanceService;
+  private final ProjectProcessService processService;
 
   static class FormMetadata {
     // 部门
@@ -132,8 +139,41 @@ public class ProcessController {
     return "/admin/business/process-review";
   }
 
-  @GetMapping("/detail")
-  public String detail(Model model) {
+  @GetMapping("/detail/{id}")
+  public String detail(@PathVariable int id, Model model) {
+    ProjectProcess process = repository.findOne(id);
+    model.addAttribute("process", process);
+
+    Project project = projectRepository.findById(process.getProjectId());
+    model.addAttribute("project", project);
+    model.addAttribute("projectType", Enumerable.of(ProjectType.class, project.getType()).getDescription());
+    model.addAttribute("cooperationType", Enumerable.of(CooperationType.class, project.getCooperateType()).getDescription());
+    List<ProcessAttachment> attachments = JSONArray.parseArray(process.getAttachmentUri(), ProcessAttachment.class);
+    model.addAttribute("attachments", attachments);
+
+    if (process.getProcessType() != null) {
+      switch (process.getProcessType()) {
+        case sale_contract: {
+          TypedQuery<SaleContract> query = entityManager.createQuery(
+                  "from SaleContract where processId=:processId", SaleContract.class);
+          query.setParameter("processId", process.getId());
+          SaleContract contract = query.getSingleResult();
+          model.addAttribute("contract", contract);
+          break;
+        }
+        case business_procurement: {
+          TypedQuery<ProcurementContract> query = entityManager.createQuery(
+                  "from ProcurementContract where processId=:processId", ProcurementContract.class);
+          query.setParameter("processId", process.getId());
+          ProcurementContract contract = query.getSingleResult();
+          model.addAttribute("contract", contract);
+          break;
+        }
+        default:
+          throw new UnsupportedOperationException("还不支持");
+      }
+    }
+
     return "/admin/business/process-detail";
   }
 
@@ -170,6 +210,8 @@ public class ProcessController {
     // 合同金额
     public BigDecimal contractAmount;
 
+    public String terminalCustomer;
+
     public List<ProjectBudgetIncomeDetail> incomeDetails;
 
     // FIXME 垫资
@@ -198,6 +240,7 @@ public class ProcessController {
             .projectNo(project.getProjectNo())
             .applyPersonName(admin.getRealName())
             .contractAmount(project.getContractAmount())
+            .terminalCustomer(project.getTerminalCustomer())
             .projectType(Enumerable.of(ProjectType.class, project.getType()).getDescription())
             .cooperationType(Enumerable.of(CooperationType.class, project.getCooperateType()).getDescription())
             .build();
@@ -208,15 +251,15 @@ public class ProcessController {
   @ResponseBody
   @PostMapping
   @Transactional
-  public void post(@RequestBody @Valid SaleContractProcessForm form) {
+  public void post(@RequestBody @Valid SaleContractProcessForm form) throws Exception {
     ProjectProcess entity = new ProjectProcess();
-    BeanUtils.copyProperties(form, entity, "sealTypes", "applyDate", "applyDept", "attachmentUri");
+    BeanUtils.copyProperties(form, entity, "sealTypes", "applyDate", "applyDept", "attachments");
     entity.setApplyDate(LocalDate.parse(form.getApplyDate(), formatter));
     entity.setSealTypes(SealTypeArray.of(form.getSealTypes()));
     entity.setApplyDept(String.join(",", form.getApplyDept()));
     Admin admin = InterfaceUtil.getAdmin();
     entity.setApplyPersonId(admin.getId());
-    entity.setAttachmentUri(JSON.toJSONString(form.getAttachmentUri()));
+    entity.setAttachmentUri(JSON.toJSONString(form.getAttachments()));
 
     if (entity.getStatus() == null) {
       entity.setStatus(ProcessStatus.to_be_audit);
@@ -224,17 +267,34 @@ public class ProcessController {
 
     entityManager.persist(entity);
 
+    HashMap<String, Object> variables = new HashMap<>();
     if (entity.getProcessType() != null) {
       switch (entity.getProcessType()) {
         case sale_contract:
           SaleContract saleContract = SaleContract.from(form);
           saleContract.setProcessId(entity.getId());
           entityManager.persist(saleContract);
+          variables.put("contract", saleContract);
           break;
         case business_procurement:
           ProcurementContract procurementContract = ProcurementContract.from(form);
           procurementContract.setProcessId(entity.getId());
           entityManager.persist(procurementContract);
+          variables.put("contract", procurementContract);
+          break;
+        default:
+          throw new UnsupportedOperationException("还不支持");
+      }
+    }
+
+    if (entity.getStatus() == ProcessStatus.to_be_audit) {
+      variables.put("process", entity);
+      switch (entity.getProcessType()) {
+        case sale_contract:
+          projectInstanceService.startSaleContractProcess(entity.getId(), variables);
+          break;
+        case business_procurement:
+          projectInstanceService.startBusinessPurchaseProcess(entity.getId(), variables);
           break;
         default:
           throw new UnsupportedOperationException("还不支持");
@@ -302,21 +362,25 @@ public class ProcessController {
   @ResponseBody
   @PostMapping("/audit")
   public void audit(@RequestBody @Valid AuditForm form) {
-
-  }
-
-  @ResponseBody
-  @GetMapping("/detail/{id}")
-  public ProjectProcess get(@PathVariable("id") int id, Model model) {
-//    model.addAttribute();
-    return entityManager.find(ProjectProcess.class, id);
+    ProjectProcess process = repository.findOne(form.processId);
+    // 2-通过  3-不通过
+    int approveType = form.processStatus == ProcessStatus.audit_passed ? 2 :
+                      form.processStatus == ProcessStatus.audit_not_passed ? 3 : -1;
+    switch (process.getProcessType()) {
+      case business_procurement:
+        projectInstanceService.completeBusinessPurchaseTask(process.getId(), approveType, form.auditOpinion);
+        break;
+      case sale_contract:
+        projectInstanceService.completeSaleContractTask(process.getId(), approveType, form.auditOpinion);
+        break;
+    }
   }
 
   @ResponseBody
   @PostMapping("/revoke/{id}")
   public void revoke(@PathVariable("id") int id) {
     // TODO 发起申请的人,在第一个人还没审批的情况下可以撤回
-    jdbcTemplate.update("update project_process set `status` =? where id =? ", ProcessStatus.draft.getValue(), id);
+    processService.updateProcessStatus(id, ProcessStatus.draft);
   }
 
   @ResponseBody
diff --git a/src/main/java/cn/palmte/work/model/process/ProcessAttachment.java b/src/main/java/cn/palmte/work/model/process/ProcessAttachment.java
new file mode 100644
index 0000000..1324a4f
--- /dev/null
+++ b/src/main/java/cn/palmte/work/model/process/ProcessAttachment.java
@@ -0,0 +1,15 @@
+package cn.palmte.work.model.process;
+
+import lombok.Data;
+
+/**
+ * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
+ * @since 2.0 2022/12/22 17:50
+ */
+@Data
+public class ProcessAttachment {
+
+  private String name;
+  private String uri;
+
+}
diff --git a/src/main/java/cn/palmte/work/model/process/ProjectProcess.java b/src/main/java/cn/palmte/work/model/process/ProjectProcess.java
index da4a23d..2a7044b 100644
--- a/src/main/java/cn/palmte/work/model/process/ProjectProcess.java
+++ b/src/main/java/cn/palmte/work/model/process/ProjectProcess.java
@@ -79,6 +79,9 @@ public class ProjectProcess {
   // 当前审核人
   private String currentAudit;
 
+  // 当前审核人ID
+  private String currentAuditId;
+
   // 最后更新时间
 
   @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
diff --git a/src/main/java/cn/palmte/work/model/process/SealTypeArray.java b/src/main/java/cn/palmte/work/model/process/SealTypeArray.java
index 0f90d38..9f0c150 100644
--- a/src/main/java/cn/palmte/work/model/process/SealTypeArray.java
+++ b/src/main/java/cn/palmte/work/model/process/SealTypeArray.java
@@ -5,6 +5,7 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import javax.persistence.Convert;
 
@@ -37,6 +38,13 @@ public class SealTypeArray {
     this.sealTypes.addAll(sealTypes);
   }
 
+  @Override
+  public String toString() {
+    return sealTypes.stream()
+            .map(SealType::getDescription)
+            .collect(Collectors.joining(","));
+  }
+
   //
 
   public static SealTypeArray of(SealType... sealTypes) {
diff --git a/src/main/java/cn/palmte/work/model/process/form/SaleContractProcessForm.java b/src/main/java/cn/palmte/work/model/process/form/SaleContractProcessForm.java
index de25338..2bb95a6 100644
--- a/src/main/java/cn/palmte/work/model/process/form/SaleContractProcessForm.java
+++ b/src/main/java/cn/palmte/work/model/process/form/SaleContractProcessForm.java
@@ -8,6 +8,7 @@ import javax.validation.constraints.NotNull;
 import cn.palmte.work.model.enums.ProcessStatus;
 import cn.palmte.work.model.enums.ProcessType;
 import cn.palmte.work.model.enums.ProcurementMode;
+import cn.palmte.work.model.process.ProcessAttachment;
 import lombok.Data;
 
 /**
@@ -76,5 +77,5 @@ public class SaleContractProcessForm {
 
   private String supplierName;
 
-  private List<String> attachmentUri;
+  private List<ProcessAttachment> attachments;
 }
diff --git a/src/main/java/cn/palmte/work/service/ProjectProcessService.java b/src/main/java/cn/palmte/work/service/ProjectProcessService.java
new file mode 100644
index 0000000..3a44f4c
--- /dev/null
+++ b/src/main/java/cn/palmte/work/service/ProjectProcessService.java
@@ -0,0 +1,64 @@
+package cn.palmte.work.service;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.persistence.EntityManager;
+import javax.persistence.TypedQuery;
+
+import cn.palmte.work.model.Admin;
+import cn.palmte.work.model.enums.ProcessStatus;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
+ * @since 2.0 2022/12/23 09:39
+ */
+@Service
+@RequiredArgsConstructor
+public class ProjectProcessService {
+
+  private final JdbcTemplate jdbcTemplate;
+  private final EntityManager entityManager;
+
+  /**
+   * 更新流程 审批人,和状态
+   *
+   * @param processId 流程Id
+   * @param auditId 审批人ID
+   * @param status 流程状态 可以为空,为空的时候不修改
+   */
+  public void updateAudit(int processId, ProcessStatus status, List<Integer> auditId) {
+    String currentAudit = getCurrentAudit(auditId);
+    String currentAuditId = auditId.stream().map(String::valueOf).collect(Collectors.joining(","));
+    if (status != null) {
+      if (!CollectionUtils.isEmpty(auditId)) {
+        jdbcTemplate.update(
+                "update project_process set current_audit=?,current_audit_id=?,status=? where id=?", currentAudit, currentAuditId, status.getValue(), processId);
+      }
+    }
+    else {
+      jdbcTemplate.update("update project_process set current_audit=?,current_audit_id=?  where id=?", currentAudit, currentAuditId, processId);
+    }
+  }
+
+  private String getCurrentAudit(List<Integer> auditId) {
+    TypedQuery<Admin> query = entityManager.createQuery("from Admin where id in (:ids)", Admin.class);
+    query.setParameter("ids", auditId);
+    List<Admin> resultList = query.getResultList();
+    return resultList.stream().map(Admin::getRealName)
+            .collect(Collectors.joining(","));
+  }
+
+  /**
+   * 只更新状态
+   */
+  public void updateProcessStatus(int processId, ProcessStatus status) {
+    jdbcTemplate.update("update project_process set `status`=? where id=?", status.getValue(), processId);
+  }
+
+}
diff --git a/src/main/resources/sql/2.0.sql b/src/main/resources/sql/2.0.sql
index 0d8511c..d4f36b9 100644
--- a/src/main/resources/sql/2.0.sql
+++ b/src/main/resources/sql/2.0.sql
@@ -22,6 +22,7 @@ create table project_process
     attachment_uri         text         null comment '附件 JSON Array',
 
     current_audit          varchar(255) null comment '当前审核人',
+    current_audit_id       varchar(255) null comment '当前审核人ID逗号分割',
 
     create_at              datetime default CURRENT_TIMESTAMP comment '创建时间',
     last_update_at         datetime default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP comment '最后更新时间'
@@ -29,7 +30,7 @@ create table project_process
 );
 
 alter table project_process
-    add attachment_uri text null comment '附件 JSON Array';
+    modify current_audit_id varchar(255) null comment '当前审核人ID逗号分割';
 
 # 采购合同
 create table procurement_contract
diff --git a/src/main/resources/templates/admin/business/process-completed.ftl b/src/main/resources/templates/admin/business/process-completed.ftl
index 6518124..058f36e 100644
--- a/src/main/resources/templates/admin/business/process-completed.ftl
+++ b/src/main/resources/templates/admin/business/process-completed.ftl
@@ -96,7 +96,7 @@
                 <span>{{scope.row.status | processStatus}}</span>
               </template>
             </el-table-column>
-            <el-table-column prop="amount" label="当前审核人" width="100"></el-table-column>
+            <el-table-column prop="currentAudit" label="当前审核人" width="100"></el-table-column>
             <el-table-column prop="lastUpdateAt" label="最后更新时间" width="170"></el-table-column>
 
             <el-table-column label="操作" fixed="right" width="230">
@@ -186,7 +186,7 @@
       showDetail(row, scope) {
         console.log(row)
         console.log(scope)
-
+        window.location = "${base}/process/detail/" + row.id;
       },
       editProcess(row, scope) {
 
diff --git a/src/main/resources/templates/admin/business/process-detail.ftl b/src/main/resources/templates/admin/business/process-detail.ftl
new file mode 100644
index 0000000..2fc5f78
--- /dev/null
+++ b/src/main/resources/templates/admin/business/process-detail.ftl
@@ -0,0 +1,648 @@
+<#assign base=request.contextPath />
+<#import "../../common/defaultLayout.ftl" as defaultLayout>
+<@defaultLayout.layout>
+<#--  <link rel="stylesheet" href="../assets/css/amazeui.switch.css"/>-->
+  <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
+  <style>
+
+      #businessPurchaseDetailsModal {
+          overflow: auto;
+      }
+
+      #businessPurchaseDetailsModal > table {
+
+      }
+
+      #newBusinessProcurementContractProcess {
+          overflow: auto;
+      }
+
+      .el-upload__input {
+          display: none !important;
+      }
+
+      .el-textarea .el-input__count {
+          line-height: 15px;
+      }
+
+      .admin-content-body {
+          margin-bottom: 100px;
+      }
+
+      .el-table__empty-block {
+          height: 60px !important;
+      }
+
+      .el-upload-list__item-name [class^="el-icon"] {
+          height: unset;
+      }
+  </style>
+
+  <div class="admin-content" id="app">
+    <div class="admin-content-body">
+      <div class="am-cf am-padding">
+        <div class="am-fl am-cf"><strong class="am-text-primary am-text-lg">业务应用</strong> /
+          <small>流程详情</small></div>
+      </div>
+
+        <#-- 新增销售合同流程 -->
+
+      <div class="am-u-sm-12 am-u-md-12">
+        <el-form :inline="true" ref="saleContractProcessForm" :model="processForm" label-position="right" label-width="100px">
+
+          <div class="am-form-inline">
+
+            <el-form-item label="项目标题">
+              <span>${process.projectNo}</span>
+            </el-form-item>
+
+            <el-tooltip effect="light" :content="projectTitle" placement="top-start">
+              <el-form-item label="项目标题">
+                <span>${process.projectTitle}</span>
+              </el-form-item>
+            </el-tooltip>
+
+            <el-form-item label="申请时间">
+              <span>${process.applyDate}</span>
+            </el-form-item>
+
+            <el-form-item label="项目类型">
+              <span>${projectType}</span>
+            </el-form-item>
+
+            <el-form-item label="合作类型">
+              <span>${cooperationType}</span>
+            </el-form-item>
+
+          </div>
+
+          <div>
+
+            <el-form-item label="申请部门">
+              <span>${process.applyDept}</span>
+            </el-form-item>
+
+            <el-form-item label="申请人">
+              <span>${process.applyPersonName}</span>
+            </el-form-item>
+
+            <el-form-item label="申请部门领导">
+              <span>${process.applyDeptLeaderName}</span>
+            </el-form-item>
+
+            <el-form-item label="申请人电话" prop="applyPersonPhone">
+              <span>${contract.applyPersonPhone}</span>
+            </el-form-item>
+
+          </div>
+
+          <div>
+
+            <el-form-item label="合同编号">
+              <span>${process.contractNo}</span>
+            </el-form-item>
+
+            <el-form-item label="合同名称">
+              <span>${process.contractName}</span>
+            </el-form-item>
+
+            <el-form-item label="合同金额">
+              <span>${project.contractAmount}</span>
+            </el-form-item>
+
+          </div>
+
+          <div>
+            <el-form-item label="客户名称" :rules="[{ required: true, message: '客户名称不能为空'}]">
+              <span>${contract.clientName}</span>
+            </el-form-item>
+
+            <el-form-item label="最终用户名称">
+              <span>${project.terminalCustomer}</span>
+            </el-form-item>
+          </div>
+
+          <div>
+            <el-form-item label="用印类型" :rules="[{ required: true, message: '用印类型不能为空'}]">
+              <span>${process.sealTypes}</span>
+            </el-form-item>
+          </div>
+
+          <div>
+
+            <el-form-item label="税率">
+              <span>${process.taxRate}</span>
+            </el-form-item>
+
+            <el-form-item label="是否垫资">
+              <span>${process.isPrepaid}</span>
+            </el-form-item>
+
+            <el-form-item label="垫资金额">
+              <span>${process.repaidAmount}</span>
+            </el-form-item>
+
+            <el-form-item label="预算毛利率">
+              <span>${process.budgetGrossMargin}</span>
+            </el-form-item>
+
+          </div>
+
+          <div>
+            <el-form-item label="收款条件">
+              <el-input type="textarea" disabled :autosize="{ minRows: 3, maxRows: 10}" cols="90" maxlength="5000" show-word-limit
+                        placeholder="请输入收款条件(限制5000字)">
+                  ${process.paymentTerms}
+              </el-input>
+            </el-form-item>
+          </div>
+
+          <div>
+            <el-form-item label="备注">
+              <el-input type="textarea" :autosize="{ minRows: 3, maxRows: 10}" maxlength="5000" show-word-limit
+                        placeholder="请输入备注(限制5000字)" cols="90">
+                  ${process.remark}
+              </el-input>
+            </el-form-item>
+          </div>
+
+          <div>
+            <el-form-item label="附件">
+                <#list attachments as attachment>
+                  <a href="${attachment.uri}">${attachment.name}</a>
+                </#list>
+            </el-form-item>
+          </div>
+
+        </el-form>
+
+          <#-- <el-row justify="space-around" type="flex" class="row-bg">-->
+        <el-row>
+          <el-button type="info" @click="goToHome">返回上一级</el-button>
+          <el-button type="primary" @click="saveDraft">保存草稿</el-button>
+          <el-button type="success" @click="submit">提交</el-button>
+        </el-row>
+
+      </div>
+
+    </div>
+
+  </div>
+
+  </div>
+
+  <script src="https://unpkg.com/vue@2/dist/vue.js"></script>
+  <script src="https://unpkg.com/element-ui/lib/index.js"></script>
+
+  <script>
+
+    const newBusinessProcurementContractProcess = "newBusinessProcurementContractProcess"
+    const saleContractProcess = "saleContractProcess"
+    const saleContractDetail = "saleContractDetail"
+    const BUTTON = "btn"
+
+    const isEmpty = (obj) => {
+      return !obj || (obj.length && obj.length === 0)
+    }
+
+    const isNotEmpty = (obj) => {
+      return !isEmpty(obj)
+    }
+
+    const isBlank = (obj) => {
+      return isEmpty(obj) || (obj.trim && isEmpty(obj.trim()))
+    }
+
+    const hasText = (obj) => {
+      return !isBlank(obj)
+    }
+
+    const data = () => {
+      return {
+        mode: "btn", // btn , newBusinessProcurementContractProcess
+        processForm: {
+          sealTypes: [],
+        },
+        projectSelected: false,
+        applySectorOptions: [
+          {
+            value: 'zhinan',
+            label: '指南',
+            children: [{
+              value: 'shejiyuanze',
+              label: '设计原则',
+              children: [{
+                value: 'yizhi',
+                label: '一致'
+              }, {
+                value: 'fankui',
+                label: '反馈'
+              }, {
+                value: 'xiaolv',
+                label: '效率'
+              }, {
+                value: 'kekong',
+                label: '可控'
+              }]
+            }, {
+              value: 'daohang',
+              label: '导航',
+              children: [{
+                value: 'cexiangdaohang',
+                label: '侧向导航'
+              }, {
+                value: 'dingbudaohang',
+                label: '顶部导航'
+              }]
+            }]
+          },
+          {
+            value: 'zujian',
+            label: '组件',
+            children: [{
+              value: 'basic',
+              label: 'Basic',
+              children: [{
+                value: 'layout',
+                label: 'Layout 布局'
+              }, {
+                value: 'color',
+                label: 'Color 色彩'
+              }, {
+                value: 'typography',
+                label: 'Typography 字体'
+              }, {
+                value: 'icon',
+                label: 'Icon 图标'
+              }, {
+                value: 'button',
+                label: 'Button 按钮'
+              }]
+            }, {
+              value: 'form',
+              label: 'Form',
+              children: [{
+                value: 'radio',
+                label: 'Radio 单选框'
+              }, {
+                value: 'checkbox',
+                label: 'Checkbox 多选框'
+              }, {
+                value: 'input',
+                label: 'Input 输入框'
+              }, {
+                value: 'input-number',
+                label: 'InputNumber 计数器'
+              }, {
+                value: 'select',
+                label: 'Select 选择器'
+              }, {
+                value: 'cascader',
+                label: 'Cascader 级联选择器'
+              }, {
+                value: 'switch',
+                label: 'Switch 开关'
+              }, {
+                value: 'slider',
+                label: 'Slider 滑块'
+              }, {
+                value: 'time-picker',
+                label: 'TimePicker 时间选择器'
+              }, {
+                value: 'date-picker',
+                label: 'DatePicker 日期选择器'
+              }, {
+                value: 'datetime-picker',
+                label: 'DateTimePicker 日期时间选择器'
+              }, {
+                value: 'upload',
+                label: 'Upload 上传'
+              }, {
+                value: 'rate',
+                label: 'Rate 评分'
+              }, {
+                value: 'form',
+                label: 'Form 表单'
+              }]
+            }, {
+              value: 'data',
+              label: 'Data',
+              children: [{
+                value: 'table',
+                label: 'Table 表格'
+              }, {
+                value: 'tag',
+                label: 'Tag 标签'
+              }, {
+                value: 'progress',
+                label: 'Progress 进度条'
+              }, {
+                value: 'tree',
+                label: 'Tree 树形控件'
+              }, {
+                value: 'pagination',
+                label: 'Pagination 分页'
+              }, {
+                value: 'badge',
+                label: 'Badge 标记'
+              }]
+            }, {
+              value: 'notice',
+              label: 'Notice',
+              children: [{
+                value: 'alert',
+                label: 'Alert 警告'
+              }, {
+                value: 'loading',
+                label: 'Loading 加载'
+              }, {
+                value: 'message',
+                label: 'Message 消息提示'
+              }, {
+                value: 'message-box',
+                label: 'MessageBox 弹框'
+              }, {
+                value: 'notification',
+                label: 'Notification 通知'
+              }]
+            }, {
+              value: 'navigation',
+              label: 'Navigation',
+              children: [{
+                value: 'menu',
+                label: 'NavMenu 导航菜单'
+              }, {
+                value: 'tabs',
+                label: 'Tabs 标签页'
+              }, {
+                value: 'breadcrumb',
+                label: 'Breadcrumb 面包屑'
+              }, {
+                value: 'dropdown',
+                label: 'Dropdown 下拉菜单'
+              }, {
+                value: 'steps',
+                label: 'Steps 步骤条'
+              }]
+            }, {
+              value: 'others',
+              label: 'Others',
+              children: [{
+                value: 'dialog',
+                label: 'Dialog 对话框'
+              }, {
+                value: 'tooltip',
+                label: 'Tooltip 文字提示'
+              }, {
+                value: 'popover',
+                label: 'Popover 弹出框'
+              }, {
+                value: 'card',
+                label: 'Card 卡片'
+              }, {
+                value: 'carousel',
+                label: 'Carousel 走马灯'
+              }, {
+                value: 'collapse',
+                label: 'Collapse 折叠面板'
+              }]
+            }]
+          },
+          {
+            value: 'ziyuan',
+            label: '资源',
+            children: [{
+              value: 'axure',
+              label: 'Axure Components'
+            }, {
+              value: 'sketch',
+              label: 'Sketch Templates'
+            }, {
+              value: 'jiaohu',
+              label: '组件交互文档'
+            }]
+          }
+        ],
+        fileList: [],
+        // 销售合同收入明细
+        incomeDetails: [],
+        attachmentUri: [],
+        attachmentMap: {}
+      }
+    }
+
+    const methods = {
+      changeMode(mode) {
+        this.mode = mode
+      },
+
+      render(obj) {
+        console.log(obj)
+      },
+
+      initForm(form) {
+        this.processForm = { ...form, sealTypes: [] }
+      },
+
+      queryProject(q, callback) {
+        if (isBlank(q)) {
+          return
+        }
+        fetch("${base}/process/projects?q=" + q)
+            .then(res => res.json())
+            .then(data => {
+              if (data.length === 0) {
+                callback([{
+                  name: '未搜索到结果'
+                }])
+              }
+              else {
+                callback(data)
+              }
+            })
+            .catch(err => {
+              this.$message.error('项目搜索失败');
+            })
+      },
+      handleSelectProject(selected) {
+        const { id, name } = selected
+        if (!id) {
+          this.initForm({})
+          return
+        }
+        const loading = this.$loading({
+          lock: true,
+          text: '正在加载项目',
+          spinner: 'el-icon-loading',
+          background: 'rgba(0, 0, 0, 0.7)'
+        })
+
+        fetch("${base}/process/projects/" + id)
+            .then(res => res.json())
+            .then(data => {
+              const { incomeDetails, ...form } = data
+              // 转换数据
+              // @formatter:off
+              const computeType = (type) => {
+                switch (type) {
+                  case 1: return '设备'
+                  case 2: return '工程'
+                  case 3: return '服务'
+                  default: return '未知'
+                }
+              }
+              // @formatter:on
+
+              this.initForm(form)
+              this.projectSelected = true
+              this.incomeDetails = incomeDetails.map(detail => ({
+                ...detail, type: computeType(detail.type)
+              }))
+            })
+            .catch(err => {
+              this.$message.error("项目'" + name + "'加载失败");
+            })
+            .finally(() => loading.close())
+      },
+      clearProjectProcess() {
+        this.projectSelected = false
+        this.initForm({})
+        this.incomeDetails = []
+      },
+
+      saveDraft() {
+        this.processForm.status = 'draft'
+        this.submit()
+      },
+
+      submit() {
+        this.$refs["saleContractProcessForm"].validate((valid) => {
+          if (valid) {
+            const fileList = this.fileList
+            console.log(fileList)
+            if (fileList.length === 0) {
+              this.$message.error("未上传附件");
+              return false
+            }
+            const loading = this.$loading({
+              lock: true,
+              text: '正在提交',
+              spinner: 'el-icon-loading',
+              background: 'rgba(0, 0, 0, 0.7)'
+            })
+
+            const form = {
+              ...this.processForm,
+              processType: 'sale_contract',
+              projectTitle: this.projectTitle,
+              attachmentUri: fileList.map(file => (file.response.data.url)),
+              incomeDetails: this.incomeDetails.map(detail => ({
+                id: detail.id, expirationDate: detail.expirationDate
+              }))
+            }
+
+            fetch("${base}/process", {
+              method: 'POST', // or 'PUT'
+              headers: {
+                'Content-Type': 'application/json',
+              },
+              body: JSON.stringify(form),
+            }).then(response => {
+              if (response.ok) {
+                this.$message({
+                  showClose: true,
+                  message: '提交成功',
+                  type: 'success'
+                })
+              }
+              else {
+                return Promise.reject("失败")
+              }
+            }).catch(err => {
+              this.$message.error("项目提交失败");
+            }).finally(() => loading.close())
+          }
+          else {
+            return false;
+          }
+        })
+      },
+
+      submitToSaleContractProcess() {
+        this.goToSaleContractProcess()
+      },
+
+      handleRemove(file, fileList) {
+        this.fileList = fileList
+      },
+
+      handleExceed(files, fileList) {
+        this.$message.warning("当前限制选择只能选择10个文件");
+        this.fileList = fileList
+      },
+
+      beforeRemove(file, fileList) {
+        return this.$confirm("确定移除 " + file.name + "?");
+      },
+
+      handleFileUploaded(response, file, fileList) {
+        if (response.success) {
+          this.fileList = fileList
+        }
+        else {
+          this.$message.warning("上传失败");
+        }
+      },
+
+      indexMethod(index) {
+        return index * 1;
+      }
+    }
+
+    new Vue({
+      el: '#app',
+      data,
+      computed: {
+        projectTitle() {
+          const { projectNo, projectName, applyPersonName, applyDate } = this.processForm
+          if (projectNo && projectName) {
+            return projectNo.trim() + "-" + projectName.trim() + "-" + applyPersonName + "-" + applyDate
+          }
+          return ""
+        },
+        isButtonMode() {
+          return this.mode === BUTTON
+        },
+        isBusinessProcurementContractProcessMode() {
+          return this.mode === newBusinessProcurementContractProcess
+        },
+        isSalesContractProcessMode() {
+          return this.mode === saleContractProcess
+        },
+        isSaleContractDetailMode() {
+          return this.mode === saleContractDetail
+        },
+        subTitle() {
+          switch (this.mode) {
+            case BUTTON:
+              return "新增流程"
+            case saleContractProcess:
+              return "新增销售合同流程"
+            case saleContractDetail:
+              return "销售合同清单明细"
+            case newBusinessProcurementContractProcess:
+              return "新增业务采购合同流程"
+          }
+        }
+      },
+
+      methods,
+
+      mounted() {
+        this.handleSelectProject({ id: 135, name: '' })
+      },
+    })
+
+  </script>
+
+</@defaultLayout.layout>
diff --git a/src/main/resources/templates/admin/business/process-new.ftl b/src/main/resources/templates/admin/business/process-new.ftl
index 142aff2..9092b3c 100644
--- a/src/main/resources/templates/admin/business/process-new.ftl
+++ b/src/main/resources/templates/admin/business/process-new.ftl
@@ -157,7 +157,7 @@
 
               <el-form-item label="最终用户名称">
                   <#--TODO 最终用户名称-->
-                <span v-if="projectSelected">{{processForm.finalUserName}}</span>
+                <span v-if="projectSelected">{{processForm.terminalCustomer}}</span>
                 <span v-else>未选择项目</span>
               </el-form-item>
             </div>
@@ -751,7 +751,10 @@
               ...this.processForm,
               processType: 'sale_contract',
               projectTitle: this.projectTitle,
-              attachmentUri: fileList.map(file => (file.response.data.url)),
+              attachments: fileList.map(file => ({
+                uri: file.response.data.url,
+                name: file.response.data.originName
+              })),
               incomeDetails: this.incomeDetails.map(detail => ({
                 id: detail.id, expirationDate: detail.expirationDate
               }))
diff --git a/src/main/resources/templates/admin/business/process-review.ftl b/src/main/resources/templates/admin/business/process-review.ftl
index 274c5da..570325a 100644
--- a/src/main/resources/templates/admin/business/process-review.ftl
+++ b/src/main/resources/templates/admin/business/process-review.ftl
@@ -85,7 +85,7 @@
                 <span>{{scope.row.status | processStatus}}</span>
               </template>
             </el-table-column>
-            <el-table-column prop="amount" label="当前审核人" width="100"></el-table-column>
+            <el-table-column prop="currentAudit" label="当前审核人" width="100"></el-table-column>
             <el-table-column prop="lastUpdateAt" label="最后更新时间" width="170"></el-table-column>
 
             <el-table-column label="操作" fixed="right" width="140">
@@ -175,7 +175,7 @@
       showDetail(row, scope) {
         console.log(row)
         console.log(scope)
-
+        window.location = "${base}/process/detail/" + row.id;
       },
       auditProcess(row, scope) {
         this.auditForm = {
diff --git a/src/test/java/cn/palmte/work/service/ProjectProcessServiceTests.java b/src/test/java/cn/palmte/work/service/ProjectProcessServiceTests.java
new file mode 100644
index 0000000..6da307b
--- /dev/null
+++ b/src/test/java/cn/palmte/work/service/ProjectProcessServiceTests.java
@@ -0,0 +1,27 @@
+package cn.palmte.work.service;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.util.Arrays;
+
+/**
+ * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
+ * @since 2.0 2022/12/23 10:42
+ */
+@SpringBootTest
+@RunWith(SpringRunner.class)
+public class ProjectProcessServiceTests {
+
+  @Autowired
+  ProjectProcessService projectProcessService;
+
+//  @Test
+  public void testUpdateAudit() {
+    projectProcessService.updateAudit(2, null, Arrays.asList(93, 94));
+  }
+
+}
\ No newline at end of file