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;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.convert.QueryByExamplePredicateBuilder;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Predicate;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;

import cn.palmte.work.ErrorMessageException;
import cn.palmte.work.bean.BudgetBean;
import cn.palmte.work.config.activiti.ActApproveTypeEnum;
import cn.palmte.work.config.activiti.ActProjectTypeEnum;
import cn.palmte.work.model.Admin;
import cn.palmte.work.model.DeptRepository;
import cn.palmte.work.model.Project;
import cn.palmte.work.model.ProjectBudgetIncomeDetail;
import cn.palmte.work.model.ProjectRepository;
import cn.palmte.work.model.ProjectTaskRecord;
import cn.palmte.work.model.SysRole;
import cn.palmte.work.model.enums.CooperationType;
import cn.palmte.work.model.enums.Enumerable;
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.enums.ProjectType;
import cn.palmte.work.model.enums.SealType;
import cn.palmte.work.model.process.BudgetPurchaseAmount;
import cn.palmte.work.model.process.BudgetPurchaseAmountModel;
import cn.palmte.work.model.process.BudgetPurchaseDetail;
import cn.palmte.work.model.process.BudgetPurchaseDetailModel;
import cn.palmte.work.model.process.ProcessAttachment;
import cn.palmte.work.model.process.ProcurementContract;
import cn.palmte.work.model.process.ProcurementDetail;
import cn.palmte.work.model.process.ProjectProcess;
import cn.palmte.work.model.process.ProjectProcessDetail;
import cn.palmte.work.model.process.ProjectProcessRepository;
import cn.palmte.work.model.process.SaleContract;
import cn.palmte.work.model.process.SealTypeArray;
import cn.palmte.work.model.process.SupplierMaterial;
import cn.palmte.work.model.process.form.ProcessCreationForm;
import cn.palmte.work.model.process.form.ProcessQueryForm;
import cn.palmte.work.model.process.form.ProcessUpdateForm;
import cn.palmte.work.service.ProjectBudgetService;
import cn.palmte.work.service.ProjectInstanceService;
import cn.palmte.work.service.ProjectProcessService;
import cn.palmte.work.service.ProjectTaskRecordService;
import cn.palmte.work.utils.InterfaceUtil;
import lombok.Builder;
import lombok.Data;
import lombok.RequiredArgsConstructor;

/**
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
 * @since 1.0 2022/12/8 11:03
 */
@Controller
@RequiredArgsConstructor
@RequestMapping("/process")
public class ProcessController {
  static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

  private final ProjectRepository projectRepository;
  private final ProjectBudgetService projectBudgetService;

  private final JdbcTemplate jdbcTemplate;
  private final EntityManager entityManager;

  private final ProjectProcessRepository repository;
  private final ProjectInstanceService projectInstanceService;
  private final ProjectProcessService processService;
  private final ProjectTaskRecordService projectTaskRecordService;

  static class FormMetadata {
    // 部门

    // 印章类型
    public Map<String, String> sealTypes;

    // 税率
    public List<Integer> taxRate;
  }

  @ResponseBody
  @GetMapping("/form/metadata")
  public FormMetadata metadata() {
    FormMetadata metadata = new FormMetadata();
    metadata.sealTypes = Arrays.stream(SealType.values())
            .collect(Collectors.toMap(SealType::getDescription, Enum::name));
    metadata.taxRate = Arrays.asList(0, 1, 3, 4, 5, 6, 9, 10, 13);
    return metadata;
  }

  /**
   * 新建流程
   */
  @GetMapping("/new")
  public String newProcess(Model model) {
    model.addAttribute("sealTypes", SealType.values());
    model.addAttribute("procurementMode", ProcurementMode.values());
    model.addAttribute("taxRate", Arrays.asList(0, 1, 3, 4, 5, 6, 9, 10, 13));
    model.addAttribute("applyDeptSectorOptions", JSON.toJSONString(processService.filterDept()));
    return "/admin/business/process-new";
  }

  /**
   * 编辑流程
   */
  @GetMapping("/edit/{id}")
  public String editProcess(Model model, @PathVariable int id) {
    model.addAttribute("processId", id);
    model.addAttribute("sealTypes", SealType.values());
    model.addAttribute("procurementMode", ProcurementMode.values());
    model.addAttribute("taxRate", Arrays.asList(0, 1, 3, 4, 5, 6, 9, 10, 13));
    model.addAttribute("applyDeptSectorOptions", JSON.toJSONString(processService.filterDept()));
    return "/admin/business/process-edit";
  }

  /**
   * 已办流程
   */
  @GetMapping("/completed")
  public String completed(Model model) {
    Admin admin = getLoginUser();
    // 超级管理员，只有查看权限，不能编辑和删除除非是自己的
    model.addAttribute("adminId", admin.getId());
    model.addAttribute("isAdmin", isAdministrator(admin));
    model.addAttribute("processTypes", ProcessType.values());
    model.addAttribute("processStatus", ProcessStatus.values());
    return "/admin/business/process-completed";
  }

  /**
   * 待我审核
   */
  @GetMapping("/review")
  public String review(Model model) {
    model.addAttribute("processTypes", ProcessType.values());
    return "/admin/business/process-review";
  }

  @GetMapping("/detail/{id}")
  public String detail(@PathVariable int id, Model model, @RequestParam(defaultValue = "false") boolean forAudit) {
    model.addAttribute("processId", id);

    ProjectProcess process = obtainProjectProcess(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);
    BigDecimal repaidAmount = getRepaidAmount(process.getProjectId());
    model.addAttribute("isPrepaid", isPrepaid(repaidAmount));
    model.addAttribute("repaidAmount", repaidAmount);

    if (isAdministrator(getLoginUser())) {
      // 管理员不能审核
      model.addAttribute("forAudit", false);
    }
    else {
      model.addAttribute("forAudit", forAudit);
    }

    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 procurement_contract: {
          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";
  }

  private BigDecimal getRepaidAmount(int id) {
    return processService.getProjectRepaidAmount(id);
  }

  private BigDecimal getContractAmount(int id) {
    return processService.getContractAmount(id);
  }

  /**
   * 搜索符合条件的项目,供给选择
   */
  @ResponseBody
  @GetMapping("/projects")
  public List<Map<String, Object>> query(@RequestParam String q, @RequestParam ProcessType processType) {
    Admin admin = getLoginUser();
    List<Project> projects = processService.queryProjects(q, processType, admin);
    return projects.stream()
            .map(project -> {
              HashMap<String, Object> map = new HashMap<>();
              map.put("id", project.getId());
              map.put("name", project.getName());
              map.put("projectNo", project.getProjectNo());
              return map;
            })
            .collect(Collectors.toList());
  }

  private static Admin getLoginUser() {
    Admin admin = InterfaceUtil.getAdmin();
    if (admin == null) {
      throw ErrorMessageException.failed("未登录或登录失效");
    }
    return admin;
  }

  @Builder
  public static class ProjectReturnValue {
    public final String applyDate = LocalDate.now().format(formatter);
    public String projectNo;
    public Integer projectId;
    public String projectName;

    // 申请人
    public String applyPersonName;

    // 项目类型
    public String projectType;

    // 合作类型
    public String cooperationType;

    // 合同金额
    public BigDecimal contractAmount;

    public String terminalCustomer;

    public List<ProjectBudgetIncomeDetail> incomeDetails;
    public List<ProcurementDetail> procurementDetails;

    // FIXME 垫资
    // 是否垫资
    public String isPrepaid;

    // 垫资金额
    public String repaidAmount;

    // 预算毛利率
    public BigDecimal budgetGrossMargin;

    public Object contract;
    public ProjectProcess process;

    public List<SupplierMaterial> supplierMaterials;

  }

  /**
   * 获取调谐表单的初始数据
   *
   * @param id 项目ID
   */
  @ResponseBody
  @GetMapping("/projects/{id}")
  public ProjectReturnValue getProject(@PathVariable int id, @RequestParam ProcessType processType) {
    Project project = projectRepository.findById(id);
    Admin admin = getLoginUser();
    // project_budget_plan_detail 垫资金额计算
    BigDecimal repaidAmount = getRepaidAmount(id);

    BigDecimal contractAmount = getContractAmount(id);

    List<ProcurementDetail> procurementDetails = Collections.emptyList();
    List<ProjectBudgetIncomeDetail> incomeDetails = Collections.emptyList();
    if (processType == ProcessType.sale_contract) {
      incomeDetails = projectBudgetService.getBudgetIncomeDetail(project);
    }
    else if (processType == ProcessType.procurement_contract) {
      procurementDetails = processService.getProcurementDetails(id, null);
    }

    BudgetBean budgetBean = projectBudgetService.getBudget(project);

    return ProjectReturnValue.builder()
            .isPrepaid(isPrepaid(repaidAmount))
            .projectId(project.getId())
            .incomeDetails(incomeDetails)
            .procurementDetails(procurementDetails)
            .projectName(project.getName())
            .repaidAmount(repaidAmount + "元")
            .budgetGrossMargin(budgetBean.getProjectGrossProfitRate())
            .projectNo(project.getProjectNo())
            .applyPersonName(admin.getRealName())
            .contractAmount(contractAmount)
            .terminalCustomer(project.getTerminalCustomer())
            .projectType(Enumerable.of(ProjectType.class, project.getType()).getDescription())
            .cooperationType(Enumerable.of(CooperationType.class, project.getCooperateType()).getDescription())
            .build();
  }

  private String isPrepaid(BigDecimal repaidAmount) {
    return processService.isProjectPrepaid(repaidAmount) ? "是" : "否";
  }

  /**
   * 获取流程详情，不同类型字段有不同
   */
  @ResponseBody
  @GetMapping("/{id}")
  public ProjectProcessDetail get(@PathVariable int id) {
    ProjectProcess process = obtainProjectProcess(id);

    ProjectProcessDetail detail = new ProjectProcessDetail();
    detail.setProcessId(id);

    detail.setProcess(process);
    Project project = projectRepository.findById(process.getProjectId());
    BudgetBean budgetBean = projectBudgetService.getBudget(project);

    detail.setProject(project);
    BigDecimal repaidAmount = getRepaidAmount(process.getProjectId());
    detail.setRepaidAmount(repaidAmount);
    detail.setBudgetGrossMargin(budgetBean.getProjectGrossProfitRate());
    Object contract = null;

    List<SupplierMaterial> supplierMaterials = Collections.emptyList();
    List<ProcurementDetail> procurementDetails = Collections.emptyList();
    List<ProjectBudgetIncomeDetail> incomeDetails = Collections.emptyList();
    if (process.getProcessType() == ProcessType.sale_contract) {
      contract = processService.findSaleContract(id);
      incomeDetails = projectBudgetService.getBudgetIncomeDetail(project);
      detail.setContractAmount(getContractAmount(project.getId()));
    }
    else if (process.getProcessType() == ProcessType.procurement_contract) {
      contract = processService.findProcurementContract(id);
      supplierMaterials = processService.getSupplierMaterials(id);
      procurementDetails = processService.getProcurementDetails(project.getId(), id);
      BigDecimal contractAmount = BigDecimal.ZERO;
      for (ProcurementDetail procurementDetail : procurementDetails) {
        if (!CollectionUtils.isEmpty(procurementDetail.getPurchaseDetails())) {
          for (BudgetPurchaseDetail purchaseDetail : procurementDetail.getPurchaseDetails()) {
            BigDecimal totalTaxInclude = purchaseDetail.getTotalTaxInclude();
            if (totalTaxInclude != null) {
              contractAmount = contractAmount.add(totalTaxInclude);
            }
          }
        }
      }
      detail.setContractAmount(contractAmount);
    }

    detail.setIsPrepaid(isPrepaid(repaidAmount));
    detail.setContract(contract);
    detail.setIncomeDetails(incomeDetails);
    detail.setSupplierMaterials(supplierMaterials);
    detail.setProcurementDetails(procurementDetails);
    detail.setProjectType(Enumerable.of(ProjectType.class, project.getType()));
    detail.setCooperationType(Enumerable.of(CooperationType.class, project.getCooperateType()));
    List<ProcessAttachment> attachments = JSONArray.parseArray(process.getAttachmentUri(), ProcessAttachment.class);
    detail.setAttachments(attachments);
    return detail;
  }

  // 合同流程

  @ResponseBody
  @PostMapping
  @Transactional(rollbackFor = Exception.class)
  public void post(@RequestBody ProcessCreationForm form) throws Exception {
    Assert.notNull(form.getProjectId(), "未选择项目");
    ProcessType processType = form.getProcessType();
    Assert.notNull(processType, "流程类型不能为空");

    ProjectProcess entity = new ProjectProcess();
    BeanUtils.copyProperties(form, entity, "sealTypes", "applyDate", "attachments");
    entity.setApplyDate(LocalDate.parse(form.getApplyDate(), formatter));
    entity.setSealTypes(SealTypeArray.of(form.getSealTypes()));
    Admin admin = getLoginUser();
    entity.setApplyPersonId(admin.getId());
    entity.setAttachmentUri(JSON.toJSONString(form.getAttachments()));

    entityManager.persist(entity);

    // 保存后流程ID可以获取
    Integer processId = entity.getId();

    if (processType == ProcessType.sale_contract) {
      SaleContract contract = SaleContract.from(form);
      contract.setProcessId(processId);
      entityManager.persist(contract);

      processService.updateIncomeDetails(form.getIncomeDetails());
    }
    else if (processType == ProcessType.procurement_contract) {
      // 采购合同
      ProcurementContract contract = ProcurementContract.from(form);
      contract.setProcessId(processId);
      entityManager.persist(contract);

      Integer contractId = contract.getId();

      if (!CollectionUtils.isEmpty(form.getSupplierMaterials())) {
        for (SupplierMaterial material : form.getSupplierMaterials()) {
          material.setProcessId(processId);
          material.setContractId(contractId);

          entityManager.persist(material);
        }
      }

      if (!CollectionUtils.isEmpty(form.getPurchaseAmount())) {
        for (BudgetPurchaseAmountModel amountForm : form.getPurchaseAmount()) {
          // 草稿模式也不允许为空 (基础数据)
          Assert.notNull(amountForm.amount, "合同明细填写不完整");
          Assert.notNull(amountForm.budgetCostId, "合同明细填写不完整");
          Assert.notNull(amountForm.amountCurrent, "合同明细填写不完整");

          // 新建
          BudgetPurchaseAmount purchaseAmount = new BudgetPurchaseAmount();
          purchaseAmount.setProcessId(processId);
          purchaseAmount.setContractId(contractId);
          purchaseAmount.setAmount(amountForm.amount);
          purchaseAmount.setBudgetCostId(amountForm.budgetCostId);
          purchaseAmount.setAmountCurrent(amountForm.amountCurrent);

          // 提交时该次数量条目生效，撤回的时候 取消该状态
          purchaseAmount.setSubmit(form.getStatus() == ProcessStatus.to_be_audit);

          entityManager.persist(purchaseAmount);

          Integer amountId = purchaseAmount.getId(); // ID 自动生成或者使用之前已经有的
          // 保存成本对应的 明细
          if (!CollectionUtils.isEmpty(amountForm.details)) {
            for (BudgetPurchaseDetailModel detail : amountForm.details) {
              BudgetPurchaseDetail purchaseDetail = new BudgetPurchaseDetail();
              // 关联主键
              purchaseDetail.setAmountId(amountId);
              purchaseDetail.setProcessId(processId);
              purchaseDetail.setContractId(contractId);
              purchaseDetail.setBudgetCostId(amountForm.budgetCostId);

              BeanUtils.copyProperties(detail, purchaseDetail, "details");
              entityManager.persist(purchaseDetail);
            }
          }
        }
      }

    }

    if (form.getStatus() == ProcessStatus.to_be_audit) {
      // 发起流程
      processService.startAuditProgress(entity);
    }
  }

  @ResponseBody
  @PutMapping("/{id}")
  @Transactional(rollbackFor = Exception.class)
  public void update(@PathVariable int id, @RequestBody @Valid ProcessUpdateForm form) throws Exception {
    ProjectProcess entity = obtainProjectProcess(id);

    Integer processId = entity.getId();
    ProcessType processType = entity.getProcessType();

    entity.setTaxRate(form.getTaxRate());
    entity.setContractNo(form.getContractNo());
    entity.setContractName(form.getContractName());
    entity.setSealTypes(SealTypeArray.of(form.getSealTypes()));
    entity.setAttachmentUri(JSON.toJSONString(form.getAttachments()));

    entity.setRemark(form.getRemark());
    entity.setStatus(form.getStatus());
    entity.setApplyDept(form.getApplyDept());
    entity.setApplyDeptId(form.getApplyDeptId());
    entity.setApplyDeptLeaderId(form.getApplyDeptLeaderId());
    entity.setApplyDeptLeaderName(form.getApplyDeptLeaderName());

    entityManager.merge(entity);

    if (processType == ProcessType.sale_contract) {
      SaleContract contract = processService.findSaleContract(id);
      contract.setClientName(form.getClientName());
      contract.setPaymentTerms(form.getPaymentTerms());
      contract.setApplyPersonPhone(form.getApplyPersonPhone());
      entityManager.merge(contract);

      processService.updateIncomeDetails(form.getIncomeDetails());
    }
    else if (processType == ProcessType.procurement_contract) {
      ProcurementContract contract = processService.findProcurementContract(id);
      contract.setProcurementMode(form.getProcurementMode());
      contract.setPaymentTerms(form.getPaymentTerms());
      contract.setSupplierName(form.getSupplierName());
      entityManager.merge(contract);

      Integer contractId = contract.getId();

      // 本次表单的数据为全量数据 需要删除
      List<SupplierMaterial> supplierMaterialsToRemove = processService.getSupplierMaterials(processId);
      if (!CollectionUtils.isEmpty(form.supplierMaterials)) {
        for (SupplierMaterial material : form.supplierMaterials) {
          material.setProcessId(processId);
          material.setContractId(contractId);
          // 更新或者新增
          if (material.getId() != null) {
            // 更新了某个ID的数据就不删除了
            supplierMaterialsToRemove.remove(material);
            entityManager.merge(material);
          }
          else {
            entityManager.persist(material);
          }
        }
      }

      for (SupplierMaterial material : supplierMaterialsToRemove) {
        entityManager.remove(material);
      }

      List<BudgetPurchaseAmount> purchaseAmountToRemove = processService.getProcessPurchaseAmount(processId);
      List<BudgetPurchaseDetail> purchaseDetailToRemove = processService.getProcessPurchaseDetail(processId);

      if (!CollectionUtils.isEmpty(form.purchaseAmount)) {
        for (BudgetPurchaseAmountModel amountForm : form.purchaseAmount) {
          // 草稿模式也不允许为空 (基础数据), 更新ID不能为空
          Assert.notNull(amountForm.amount, "合同明细填写不完整");
          Assert.notNull(amountForm.budgetCostId, "合同明细填写不完整");

          // 新建
          BudgetPurchaseAmount purchaseAmount = new BudgetPurchaseAmount();
          purchaseAmount.setProcessId(processId);
          purchaseAmount.setContractId(contractId);
          purchaseAmount.setAmount(amountForm.amount);
          purchaseAmount.setBudgetCostId(amountForm.budgetCostId);
          purchaseAmount.setAmountCurrent(amountForm.amountCurrent);
          purchaseAmount.setSubmit(form.getStatus() == ProcessStatus.to_be_audit);

          if (amountForm.amountId != null) {
            // 更新了某个ID的数据就不删除了
            purchaseAmount.setId(amountForm.amountId);
            purchaseAmountToRemove.remove(purchaseAmount);
            entityManager.merge(purchaseAmount);
          }
          else {
            entityManager.persist(purchaseAmount);
          }

          Integer amountId = purchaseAmount.getId(); // ID 自动生成或者使用之前已经有的
          // 保存成本对应的 明细
          if (!CollectionUtils.isEmpty(amountForm.details)) {
            for (BudgetPurchaseDetailModel detail : amountForm.details) {
              BudgetPurchaseDetail purchaseDetail = new BudgetPurchaseDetail();
              // 关联主键
              purchaseDetail.setAmountId(amountId);
              purchaseDetail.setProcessId(processId);
              purchaseDetail.setContractId(contractId);
              purchaseDetail.setBudgetCostId(amountForm.budgetCostId);

              BeanUtils.copyProperties(detail, purchaseDetail, "details");

              if (detail.id != null) {
                // 更新了某个ID的数据就不删除了
                purchaseDetail.setId(detail.id);
                purchaseDetailToRemove.remove(purchaseDetail);
                entityManager.merge(purchaseDetail);
              }
              else {
                entityManager.persist(purchaseDetail);
              }
            }
          }
        }
      }

      // 删除
      for (BudgetPurchaseAmount amount : purchaseAmountToRemove) {
        entityManager.remove(amount);
      }

      for (BudgetPurchaseDetail detail : purchaseDetailToRemove) {
        entityManager.remove(detail);
      }
    }

    if (form.getStatus() == ProcessStatus.to_be_audit) {
      processService.startAuditProgress(entity);
    }
  }

  @ResponseBody
  @PostMapping("/query")
  public Page<ProjectProcess> list(@RequestBody ProcessQueryForm form, Pageable pageable,
          @RequestParam(defaultValue = "false") boolean forAudit) {
    // 从 0 开始
    PageRequest pageRequest = new PageRequest(pageable.getPageNumber() - 1, pageable.getPageSize(),
            new Sort(Sort.Direction.DESC, "lastUpdateAt", "createAt"));

    ProjectProcess projectProcess = new ProjectProcess();
    BeanUtils.copyProperties(form, projectProcess);
    projectProcess.setStatus(form.getProcessStatus());

    ExampleMatcher exampleMatcher = ExampleMatcher.matching()
            .withMatcher("contractNo", ExampleMatcher.GenericPropertyMatcher::contains)
            .withMatcher("projectNo", ExampleMatcher.GenericPropertyMatcher::contains)
            .withMatcher("projectTitle", ExampleMatcher.GenericPropertyMatcher::contains)
            .withMatcher("applyPersonName", ExampleMatcher.GenericPropertyMatcher::contains);
    Admin admin = getLoginUser();
    Integer loginUserId = admin.getId();

    // 待我审核
    if (forAudit) {
      // 超级管理员也看不到，除非该他审核
      return repository.findAll((root, query, cb) -> {
        Predicate predicate = QueryByExamplePredicateBuilder.getPredicate(root, cb, Example.of(projectProcess, exampleMatcher));
        // 添加新的条件
        Expression<Integer> findInSet = cb.function("find_in_set", Integer.class, cb.literal(loginUserId), root.get("currentAuditId"));
        return cb.and(predicate, cb.greaterThan(findInSet, 0));
      }, pageRequest);
    }

    // 已办流程

    if (isAdministrator(admin)) {
      // 超级好管理员不需要
      return repository.findAll(Example.of(projectProcess, exampleMatcher), pageRequest);
    }

    return repository.findAll((root, query, cb) -> {
      Predicate predicate = QueryByExamplePredicateBuilder.getPredicate(root, cb, Example.of(projectProcess, exampleMatcher));
      // 添加新的条件
      Predicate applyPersonId = cb.equal(root.get("applyPersonId"), loginUserId);

      // 待自己审的
      Expression<Integer> findInSet = cb.function("find_in_set", Integer.class, cb.literal(loginUserId), root.get("currentAuditId"));
      Expression<Integer> reviewedIdFindInSet = cb.function("find_in_set", Integer.class, cb.literal(loginUserId), root.get("reviewedId"));

      Predicate or = cb.or(applyPersonId,
              cb.greaterThan(reviewedIdFindInSet, 0),
              cb.greaterThan(findInSet, 0));

      return cb.and(predicate, or);
    }, pageRequest);
  }

  private boolean isAdministrator(Admin admin) {
    return admin != null && admin.getRoleType() == SysRole.ROLE_TYPE_SYSTEM;
  }

  // 审核
  @Data
  static class AuditForm {

    // 流程ID
    private Integer processId;

    // 审核意见
    @NotNull
    private String auditOpinion;

    // 审核状态
    private ProcessStatus processStatus;

  }

  @ResponseBody
  @Transactional
  @PostMapping("/audit")
  public void audit(@RequestBody @Valid AuditForm form) {
    ProjectProcess process = obtainProjectProcess(form.processId);
    ActApproveTypeEnum approveType = form.processStatus == ProcessStatus.audit_passed ? ActApproveTypeEnum.APPROVAL_PASSED :
                                     ActApproveTypeEnum.APPROVAL_UN_PASS;

    Admin admin = getLoginUser();
    String reviewedId = process.getReviewedId();

    ArrayList<String> reviewed = new ArrayList<>();
    if (reviewedId != null) {
      String[] split = reviewedId.split(",");
      Collections.addAll(reviewed, split);
    }
    reviewed.add(String.valueOf(admin.getId()));
    reviewedId = String.join(",", reviewed);
    process.setReviewedId(reviewedId);
    entityManager.merge(process);
//    jdbcTemplate.update("update project_process set reviewed_id =? where id=?", reviewed, process.getId());

    switch (process.getProcessType()) {
      case procurement_contract:
        projectInstanceService.completeTaskByProjectId(process.getId(), ActProjectTypeEnum.BUSINESS_PURCHASE, approveType, form.auditOpinion);
        break;
      case sale_contract:
        projectInstanceService.completeTaskByProjectId(process.getId(), ActProjectTypeEnum.SALE_CONTRACT, approveType, form.auditOpinion);
        break;
    }
  }

  @ResponseBody
  @PostMapping("/revoke/{id}")
  public void revoke(@PathVariable("id") int id) {
    Admin admin = getLoginUser();
    if (isAdministrator(admin)) {
      processService.revoke(id);
    }
    else {
      ProjectProcess process = obtainProjectProcess(id);
      Integer applyPersonId = process.getApplyPersonId();
      if (!Objects.equals(admin.getId(), applyPersonId)) {
        throw ErrorMessageException.failed("流程不属于自己");
      }
      List<ProjectTaskRecord> projectTaskRecords = getProjectTaskRecords(id, process.getProcessType());
      if (projectTaskRecords.size() > 1) {
        // 可能存在撤销情况 会出现 多次 -> 提交申请 提交销售合同流程
        // 第一个审核详情肯定是 提交申请 ，检查最后一个是不是 提交申请 是的话就可以撤回
        ProjectTaskRecord lastRecord = projectTaskRecords.get(projectTaskRecords.size() - 1);
        if (!isStartAuditProgress(lastRecord)) {
          // 有多个人审核过
          throw ErrorMessageException.failed("流程已经在审核中，不能撤回");
        }
      }
      processService.revoke(id);
    }
  }

  private boolean isStartAuditProgress(ProjectTaskRecord record) {
    return Objects.equals("提交申请", record.getTaskName());
  }

  private ProjectProcess obtainProjectProcess(int id) {
    ProjectProcess projectProcess = processService.getById(id);
    if (projectProcess == null) {
      throw ErrorMessageException.failed("流程不存在");
    }
    return projectProcess;
  }

  @ResponseBody
  @DeleteMapping("/{id}")
  @Transactional
  public void delete(@PathVariable("id") int id) {
    ProjectProcess process = obtainProjectProcess(id);
    Integer applyUserId = getLoginUser().getId();
    if (!Objects.equals(process.getApplyPersonId(), applyUserId)) {
      throw ErrorMessageException.failed("删除的流程不存在或者不属于自己");
    }

    if (process.getProcessType() == ProcessType.sale_contract) {
      Query query = entityManager.createQuery("delete from SaleContract where processId=:processId");
      query.setParameter("processId", id);
      query.executeUpdate();
    }
    else if (process.getProcessType() == ProcessType.procurement_contract) {
      Query query = entityManager.createQuery("delete from ProcurementContract where processId=:processId");
      query.setParameter("processId", id);
      query.executeUpdate();

      query = entityManager.createQuery("delete from SupplierMaterial where processId=:processId");
      query.setParameter("processId", id);
      query.executeUpdate();

      query = entityManager.createQuery("delete from BudgetPurchaseAmount where processId=:processId");
      query.setParameter("processId", id);
      query.executeUpdate();

      query = entityManager.createQuery("delete from BudgetPurchaseDetail where processId=:processId");
      query.setParameter("processId", id);
      query.executeUpdate();

    }

    Query query = entityManager.createQuery("delete from ProjectProcess where id=:processId");
    query.setParameter("processId", id);
    query.executeUpdate();

  }

  /**
   * 查看审核流程
   */
  @ResponseBody
  @GetMapping("/audits/{id}")
  public List<Map<String, String>> showAudits(@PathVariable("id") int id) {
//    ArrayList<Map<String, String>> ret = new ArrayList<>();
//    ret.add(create("2021-01-03 12:07:45", "提交审核"));
//    ret.add(create("2021-01-28 12:15:05", "工程运维管理部孙果审核已通过"));
//    ret.add(create("2021-02-03 22:01:12", "系统产品集成部尹浩审核已通过"));
//    ret.add(create("2021-03-03 22:05:12", "财务部主管何丹审核已通过"));

    ProjectProcess process = obtainProjectProcess(id);
    ProcessType processType = process.getProcessType();
    List<ProjectTaskRecord> projectTaskRecords = getProjectTaskRecords(id, processType);
    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    return projectTaskRecords.stream()
            .map(item -> {
              Date createTime = item.getCreateTime();
              return create(format.format(createTime), item.getTaskName() + ": " + item.getTaskComment());
            }).collect(Collectors.toList());
  }

  private List<ProjectTaskRecord> getProjectTaskRecords(int id, ProcessType processType) {
    return projectTaskRecordService.listV2(id,
            processType == ProcessType.sale_contract ? ActProjectTypeEnum.SALE_CONTRACT : ActProjectTypeEnum.BUSINESS_PURCHASE);
  }

  private Map<String, String> create(String timestamp, String content) {
    Map<String, String> map = new HashMap<>();
    map.put("content", content);
    map.put("timestamp", timestamp);
    return map;
  }

}
