package com.malk.aiwei.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.malk.aiwei.server.AWServer;
import com.malk.aiwei.server.YDSearch;
import com.malk.aiwei.service.AWClint;
import com.malk.aiwei.service.AwDingService;
import com.malk.server.aliwork.YDConf;
import com.malk.server.aliwork.YDParam;
import com.malk.server.common.McException;
import com.malk.server.dingtalk.DDConf;
import com.malk.server.dingtalk.DDR_New;
import com.malk.server.teambition.TBConf;
import com.malk.service.aliwork.YDClient;
import com.malk.service.aliwork.YDService;
import com.malk.service.dingtalk.DDClient;
import com.malk.service.dingtalk.DDClient_Contacts;
import com.malk.service.dingtalk.DDClient_Storage;
import com.malk.service.teambition.TBClient;
import com.malk.utils.*;
import lombok.Synchronized;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class AWImplClient implements AWClint {

    @Autowired
    private YDClient ydClient;

    @Autowired
    private YDService ydService;

    @Autowired
    private YDConf ydConf;

    @Autowired
    private AwDingService awDingService;

    // 单据环境映射
    private String _matchFormUuid(String code) {
        Map<String, String> formUuid = UtilMap.empty();
        if (true || UtilEnv.getActiveProfile().equals(UtilEnv.ENV_PROD) || UtilEnv.getActiveProfile().equals(UtilEnv.ENV_TEST)) {
            formUuid.put("DENTRY", "FORM-BD73A57B62EA4153B896C9BB3EA14D28GWSQ");                // 文件夹/文件版本记录
            formUuid.put("PERMISSION", "FORM-3380221DFDF84FBD8BCF503198294257TFWB");            // 文件夹权限
            formUuid.put("REVIEW", "FORM-812FD46AF391449A8F206EDB3221B38840UQ");                // 交付物审批记录
            formUuid.put("REVIEW_PROCESS", "TPROC--RJC66SC1NEFHXJ0H770K0CF4WN1K21HQ706RL5");    // 交付物审批记录
            formUuid.put("PROJECT", "FORM-141E21DF183846028E21727CE43CD1C75CLZ");               // 项目主数据
            formUuid.put("APPROVE", "FORM-A25299893F614A6EAA672514D3A76BB0QDBF");               // 交付物审批矩阵
            formUuid.put("CHECK", "FORM-1A5D4D7FBF88409B956EBE51F9342A6BKOLP");                 // 预检项
            formUuid.put("RECORD", "FORM-6E2C0D1197264B8AA23EB3FECAE7344B00BN");                // 预检项记录
            formUuid.put("RTO", "FORM-DF879E2CC586467C97203B7F16733228Y2MN");                   // RTO检查项控
            formUuid.put("ROLE", "FORM-3C7396A12ADB48A8833EBD90089C93833R21");                  // 项目角色
            formUuid.put("ROLE_PDT", "FORM-69B6A8151F3346DCA20B0ED54F5380675LJX");              // PDT角色
            formUuid.put("CRM_LOG", "FORM-16DD578308E64763AE1539D8176CCCD0GX6F");               // crm推送日志
            formUuid.put("TEMPLATE", "FORM-24EFE5C1BCE54E7192E53B33ADFBF1C52NS3");              // TB模板与项目类型映射表
            formUuid.put("DOMAIN", "https://yida.awinic.com/");                                 // 宜搭域名
        } else {
            // [ fixme: 测试环境添加主数据, 匹配不同架构下企业成员 ]
            formUuid.put("DENTRY", "FORM-AA1237FC3B5A4DEBA7C823EDB48CCDEBZ1SV");
            formUuid.put("PERMISSION", "FORM-E06FB2317C3A4C119B96EE2B034AD3A8O3YF");
            formUuid.put("REVIEW", "FORM-FBC1A390B4C348089020C763938A6F54RUNY");
            formUuid.put("REVIEW_PROCESS", "TPROC--YU966T91PIDH4XDR82OJC8GVOP7Z19PODTXQL6");
            formUuid.put("PROJECT", "FORM-84EF78C7DBA047E58A8C8511106F91D5WNVI");
            formUuid.put("APPROVE", "FORM-AB7263D2A12F4E01A871656F0D995BC96PR1");
            formUuid.put("CHECK", "FORM-E6CB042D7929448888AE5E2B27631E57IVPM");
            formUuid.put("RECORD", "FORM-7B63BB056145452F8BC0A2C52492DE00QVBH");
            formUuid.put("ROLE", "FORM-5BE21392886E46DF955D1EBC100ADA429NON");
            formUuid.put("ROLE_PDT", "FORM-EE138B018F5F4B569279B299D43AA77E2QL1");
            formUuid.put("CRM_LOG", "FORM-7F40E85D4FA8487C8E00C9156B76A953CBMG");
            formUuid.put("TEMPLATE", "FORM-C864290977A64FC4A061434DF21D5871JA04");
            formUuid.put("DOMAIN", "https://kabom7.aliwork.com/");
        }
        return formUuid.get(code);
    }

    /**
     * 交付物审批 [ppExt: 宜搭附件传递 downloadUrl 和 name 即可实现在线预览]
     * -
     * 6.18 变更说明
     * 1. 提交发布与提交变更均通过任务按钮操作, 本质上链到宜搭的页面
     * 2. 提交发布自动触发评审, 执行结束关闭tab页面. 若有报错则弹出错误信息
     * 3. 提交变更链接到交付物审批页面, 去掉所有的拒绝, 开启退回操作. 并添加 PCR 填写
     */
    @Override
    public Map doApprove(Map data, boolean isChange) {

        log.info("交付物审批, {}", data);
        String taskId = UtilMap.getString(data, "taskId");
        String title = UtilMap.getString(data, "title");

        // prd 618 通过任务扩展按钮触发, 接口仅需传递 taskId 即可, 相关信息直接从任务详情取值
        Map taskData = _getTaskFieldMap(taskId, AWServer.TASK_CODE, AWServer.TASK_APPROVE_ATTACHMENT, AWServer.TASK_APPROVE_DESC, AWServer.TASK_PRODUCT, AWServer.TASK_PRODUCT_VERSION, AWServer.TASK_CHECK_STATUS);
        Map rTask = UtilMap.getMap(taskData, "task");
        String pCode = UtilMap.getString(rTask, "projectId");

        // 通过工作流触发: prd 618 通过任务扩展按钮触发, fixme tb任务完成态工作流工作会触发完成时间更新, 导致逾期问题
        String type = isChange ? "变更" : "发布"; // 回调 发布 变更
        boolean isCallback = data.containsKey("tfsId");
        if (isCallback) {
            String projectId = UtilMap.getString(data, "projectId");
            String workFlowApprove = _getWorkFlowStatus(projectId, AWServer.WORKFLOW_APPROVE);
            if (!data.get("tfsId").equals(workFlowApprove)) {
                return null;
            }
            type = "回调";
        } else {
            // prd 发起评审先修改工作流，成功后再执行相关逻辑，避免前后置依赖与子任务未完成导致不能关闭
            String workFlowId_c = _getWorkFlowStatus(pCode, "完成");
            if (!isChange) {
                McException.assertAccessException(StringUtils.isBlank(UtilMap.getString(taskData, AWServer.TASK_CHECK_STATUS)), "请核对, 技术检查项检查状态!");
                // prd: 不过滤审批中, 避免撤销后重新填写交付件情况
                McException.assertAccessException(workFlowId_c.equals(rTask.get("tfsId")), "已完成任务, 不允许提交发布!");
                String workFlowId = _getWorkFlowStatus(pCode, "审批中");
                tbClient.updateTaskFlowStatus(taskId, tbConf.getOperatorId(), workFlowId, "审批中");
            } else {
                McException.assertAccessException(!rTask.get("tfsId").equals(workFlowId_c), "非已完成任务, 不允许提交变更!");
            }
        }
        List<Map> docs = _getDocs(taskData, AWServer.TASK_APPROVE_ATTACHMENT);
        // prd 7.10 交付件描述为空，即非交付件审批任务；且未传交付件内容，直接更新为审批通过。否则走交付物审批升版
        if (UtilMap.isBlankString(taskData, AWServer.TASK_APPROVE_DESC) && docs.size() == 0) {
            String workFlowId = _getWorkFlowStatus(pCode, "完成");
            tbClient.updateTaskFlowStatus(taskId, tbConf.getOperatorId(), workFlowId, "完成");
            Map body = TBConf.assembleCustomFieldName(AWServer.TASK_APPROVE_LINK, "非交付件任务");
            tbClient.updateTaskCustomField(taskId, tbConf.getOperatorId(), body);
//            _updateApproveField(taskId, new ArrayList<>(), "", "非交付件任务", "已终止", AWServer.TASK_APPROVE_LINK);
            return null;
        }
        // ppExt: TB有卡控, 校验交付物不能为空, 同时兼容非交付件任务类型
        McException.assertAccessException(!isCallback && docs.size() == 0, "任务交付件不能为空, 请填写后再操作!");
        String executorId = UtilMap.getString(rTask, "executorId");
        if (!isCallback) {
            McException.assertAccessException(StringUtils.isBlank(executorId), "任务执行人未分配!");
        }

        // 返回userId, 若非执行人权限, 页面控制弹出框
        String userId = "";
        String tCode = UtilMap.getString(taskData, AWServer.TASK_CODE);
        // 工作流tb虽然配置了流转逻辑, 冗余稳定性
        List<Map> formList = (List<Map>) ydClient.queryData(YDParam.builder()
                .formUuid(_matchFormUuid("REVIEW"))
                .searchFieldJson(JSON.toJSONString(UtilMap.map("selectField_lqxuswzd, textField_lrncs2fu", tCode, pCode)))
                .instanceStatus("RUNNING")
                .build(), YDConf.FORM_QUERY.retrieve_search_process).getData();
        if (formList.size() > 0 && formList.stream().filter(item -> tCode.equals(((Map) item.get("data")).get("selectField_lqxuswzd"))).findAny().isPresent()) {
            McException.assertAccessException(true, "已存在审批中流程, 请勿重复提交!");
        }
        String result = "";
        List<Map> tList = null;
        Map rProject = null;
        if (StringUtils.isBlank(tCode)) {
            result = "无任务编码";
        } else {
            List<Map> pList = ydService.queryDataList_FormData(_matchFormUuid("PROJECT"), UtilMap.map("textField_lqxtykce", pCode));
            pList = pList.stream().filter(item -> pCode.equals(item.get("textField_lqxtykce"))).collect(Collectors.toList());
            if (pList.size() == 0) {
                result = "未匹配到项目主数据";
            } else {
                rProject = pList.get(0);
                // prd 多模板适配: [任务号 + 项目类型]
                String proType = UtilMap.getString(rProject, "textField_ltwcq7s6");
                tList = ydService.queryDataList_FormData(_matchFormUuid("APPROVE"), UtilMap.map("selectField_lrncf4hk, radioField_lrnddfq6", tCode, "启用"));
                tList = tList.stream().filter(item -> tCode.equals(item.get("selectField_lrncf4hk")) && UtilMap.getList(item, "multiSelectField_ltwjre9s").contains(proType)).collect(Collectors.toList());
                if (tList.size() == 0) {
                    result = "未配置交付物审批";
                }
            }
            log.info("交付物结果, {}", result);
            /// 发起评审 prd 630 若无交付物审批, 则配置发起人自动通过, 以兼容全部任务类型可提交交付件评审
            boolean isIgnore = "未配置交付物审批".equals(result);
            if (StringUtils.isBlank(result) || isIgnore) {
                result = ""; // 重置返回错误信息
                List<Map> roles = (List<Map>) rProject.get("tableField_lqxtykcf");
                Map formData = UtilMap.map("selectField_lqxuswzd, textField_lrncs2fu", tCode, pCode);
                formData.put("attachmentField_lqxtebtq", docs);
                formData.put("textField_lxl98hsu", UtilMap.getString(rTask, "content"));                            // 任务标题
                formData.put("selectField_lqxuswze", UtilMap.getString(rProject, "textField_lrj7vnxb"));            // 项目编号
                formData.put("textField_lqxuc9m4", UtilMap.getString(rProject, "textareaField_lrj7vnxl"));          // 项目描述
                formData.put("employeeField_ltzn872j", UtilMap.getString(rProject, "employeeField_ltzn872j_id"));   // 项目经理 0402 控制矩阵角色为空, 流转到PM
                formData.put("textField_ltwcq7s6", UtilMap.getString(rProject, "textField_ltwcq7s6"));              // 项目类型
                formData.put("textareaField_lw0nmvko", UtilMap.getString(taskData, AWServer.TASK_APPROVE_DESC));         // 交付件描述
                formData.put("textField_lvbrueqs", UtilMap.getString(taskData, AWServer.TASK_PRODUCT));                  // 产品型号
                formData.put("textField_lvbrueqt", UtilMap.getString(taskData, AWServer.TASK_PRODUCT_VERSION));          // 产品版本
                formData.put("radioField_ly2o9rxn", isIgnore ? "是" : "否");                        // 审批分支
                // 匹配任务编码与项目角色
                List<Map<String, String>> compIds = Arrays.asList(  // 任务表角色, 交付物评审表: 角色, 审批人
                        UtilMap.map("tsRole, prRole, prEmp", "multiSelectField_lrokzlo7, multiSelectField_lrokzlo7, employeeField_lqxtebtw"),
                        UtilMap.map("tsRole, prRole, prEmp", "multiSelectField_lrokzlo8, multiSelectField_lrokzlo8, employeeField_lqxtebtx"),
                        UtilMap.map("tsRole, prRole, prEmp", "multiSelectField_lrokzlo9, multiSelectField_lrokzlo9, employeeField_lqxtebty"));
                if (!isIgnore) {
                    Map rCondition = tList.get(0);
                    for (Map compId : compIds) {
                        // ppExt: 宜搭多选若无值则不返回字段 [prd 角色多选, 会签逻辑]
                        if (!rCondition.containsKey(compId.get("tsRole"))) {
                            continue;
                        }
                        List<String> namesCondition = (List<String>) rCondition.get(compId.get("tsRole"));
                        List<String> namesApproveRole = new ArrayList<>();
                        List<String> namesApprover = new ArrayList<>();
                        for (String name : namesCondition) {
                            Optional optional = roles.stream().filter(item -> UtilString.equal(name, item.get("selectField_lqxu6bgf"))).findAny();
                            if (optional.isPresent()) {
                                Map detail = (Map) optional.get();
                                if (ObjectUtil.isNotNull(detail.get("selectField_lqxu6bgf"))) {
                                    namesApproveRole.add(String.valueOf(detail.get("selectField_lqxu6bgf")));
                                }
                                if (ObjectUtil.isNotNull(detail.get("employeeField_lqxtykch_id"))) {
                                    namesApprover.addAll((List) detail.get("employeeField_lqxtykch_id"));
                                }
                            }
                        }
                        formData.put(compId.get("prRole"), namesApproveRole);
                        formData.put(compId.get("prEmp"), namesApprover);
                    }
                }
                // 组装数据
                Map<String, String> extra = (Map) tbClient.idMapQuery(executorId, "dingTalk-user", ddConf.getCorpId()).get(0).get("extra");
                UtilMap.putAll(formData, UtilMap.map("textField_lr3dlwsa, textField_lr3er4qb", taskId, executorId));
                formData.put("employeeField_lui5fu7z", Arrays.asList(extra.get("userId")));  // 0402 指定发起人, 用于结构化数据子流程发起人
                formData.put("radioField_lxkaihe5", type);
                formData.put("selectField_lxl7xtwe", isChange ? "提交变更" : "提交发布");       // 评审类型标题
                userId = extra.get("userId");
                // prd 618 通过任务扩展按钮触发, 变更填写关联pcr后再提交, 返回数据. 并需要回调
                if (!isChange) {
                    String instanceId = (String) ydClient.operateData(YDParam.builder()
                            .formUuid(_matchFormUuid("REVIEW"))
                            .processCode(_matchFormUuid("REVIEW_PROCESS"))
                            .formDataJson(JSON.toJSONString(formData))
                            .userId(extra.get("userId"))
                            .build(), YDConf.FORM_OPERATION.start);
                    result = _matchFormUuid("DOMAIN") + ydConf.getAppType() + "/processDetail?procInsId=" + instanceId;
                } else {
                    formData.put("result", result);
                    formData.put("userId", userId);
                    List<String> pmUserIds = (List<String>) JSON.parse(UtilMap.getString(rProject, "employeeField_ltzn872j_id"));
                    if (ObjectUtil.isNotNull(pmUserIds)) {
                        formData.put("PMName", ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), pmUserIds.get(0)).get("name"));
                    }
                    return formData;
                }
            }
        }
        // prd 618 通过任务扩展按钮, 提交后再触发审批回写, 直接提交场景下直接更新审批链接
        if (!isChange) {
            Map body = TBConf.assembleCustomFieldName(AWServer.TASK_APPROVE_LINK, result);
            tbClient.updateTaskCustomField(taskId, tbConf.getOperatorId(), body);
//            _updateApproveField(taskId, new ArrayList<>(), result, title, "审批中", AWServer.TASK_APPROVE_LINK);
        }
        return UtilMap.map("result, userId", result, userId);
    }

    /**
     * 交付物审批变更发起
     */
    @Override
    public void changeApprove(String taskId, String instanceId, String title) {
        String result = _matchFormUuid("DOMAIN") + ydConf.getAppType() + "/processDetail?procInsId=" + instanceId;
        Map body = TBConf.assembleCustomFieldName(AWServer.TASK_APPROVE_LINK, result);
        tbClient.updateTaskCustomField(taskId, tbConf.getOperatorId(), body);

        // todo tb回写多的审批记录仅显示第一个, 建议保存最后一个, 审批回调时更新, 避免匹配对应数据问题
//        Map taskData = _getTaskFieldMap(taskId, AWServer.TASK_APPROVE_LINK);
//        List<Map> approves = _getCustomFieldList(taskData, AWServer.TASK_APPROVE_LINK);
        _updateApproveField(taskId, new ArrayList<>(), result, title, "审批中", AWServer.TASK_APPROVE_LINK);

        Map body2 = TBConf.assembleCustomFieldName(AWServer.TASK_APPROVE_STATE, "变更中");
        tbClient.updateTaskCustomField(taskId, tbConf.getOperatorId(), body2);
    }

    /**
     * 交付物审批回调
     */
    @Override
    public void approved(Map data) {

        log.info("交付物审批回调, {}", data);
        String type = UtilMap.getString(data, "type");
        String projectId = UtilMap.getString(data, "projectId");
        String taskId = UtilMap.getString(data, "taskId");
        String title = UtilMap.getString(data, "title");
        String instanceId = UtilMap.getString(data, "instanceId");
        String result = UtilMap.getString(data, "approve").contains("通过") ? "完成" : "拒绝";
        if ("变更".equals(type)) {
            Map body = TBConf.assembleCustomFieldName(AWServer.TASK_APPROVE_STATE, "已变更");
            tbClient.updateTaskCustomField(taskId, tbConf.getOperatorId(), body);
        }
        // prd 618 兼容原逻辑, 保留回调类更新任务工作流. 新版本通过任务扩展按钮触发, 发布在创建时触发\变更不更新工作流避免导致逾期情况
        if ("回调".equals(type)) {
            String workFlowId = _getWorkFlowStatus(projectId, result);
            tbClient.updateTaskFlowStatus(taskId, tbConf.getOperatorId(), workFlowId, result);
        }
        if ("发布".equals(type)) {
            String workFlowId = _getWorkFlowStatus(projectId, result);
            tbClient.updateTaskFlowStatus(taskId, tbConf.getOperatorId(), workFlowId, result);
        }
        String url = _matchFormUuid("DOMAIN") + ydConf.getAppType() + "/processDetail?procInsId=" + instanceId;
        _updateApproveField(taskId, new ArrayList<>(), url, title, result, AWServer.TASK_APPROVE_LINK);
    }

    /**
     * 更新任务自定义审批字段值
     *
     * @apiNote https://open.teambition.com/docs/documents/664dad5aba1c5ca0e1576f8f#4%230%23%E5%AE%A1%E6%89%B9%E5%8A%9F%E8%83%BD%E9%85%8D%E7%BD%AE
     * [ ppExt ]
     * 1. 更新需要为 metaString, 若是 meta 自定义字段不生效. 目前发现仅自定义字段有此问题
     * 2. tb 内弹出框打开可用 handler, 但宜搭链接会被拦击, 直接赋值url新标签页打开
     * 3. 标题字段直接赋值, 不能添加自定义值, 如标记颜色, 否则点击连接诶后任务上该字段会消失: meta.put("title", UtilMap.map("color, value", "BLUE", "xxx"))
     */
    private void _updateApproveField(String taskId, List<Map> approves, String url, String title, String subTitle, String fieldName) {
        Map colors = UtilMap.map("待提交, 已提交, 审批中, 已通过, 已拒绝, 已终止", "ORANGE, BLUE, BLUE, GREEN, RED, RED");
        Map meta = UtilMap.map("instanceId, url", taskId, url);
        meta.put("icon", UtilMap.map("value, color, bgColor", "Icon_Approve, WHITE, ORANGE"));
        meta.put("subTitle", UtilMap.map("value, color", subTitle, colors.get(subTitle)));
        approves.add(UtilMap.map("title, metaString", title, JSON.toJSONString(meta)));
        log.info("更新任务自定义审批字段值, {}, {}", taskId, JSON.toJSONString(approves));
        tbClient.updateTaskCustomField(taskId, tbConf.getOperatorId(), UtilMap.map("customfieldName, value", fieldName, approves));
    }

    @Autowired
    private TBClient tbClient;

    @Autowired
    private DDConf ddConf;

    // 获取任务详情 fixme 提取到 tbservice
    private Map<String, String> _getTaskFieldMap(Map task, List<Map> projectCustomField, String... fieldNames) {
        return __getTaskMap(task, projectCustomField, fieldNames);
    }

    // 组装任务数据
    private Map __getTaskMap(Map task, List<Map> projectCustomField, String... fieldNames) {
        Map result = UtilMap.map("task", task);
        List<Map> taskCustomField = (List<Map>) task.get("customfields");
        for (String filed : fieldNames) {
            Optional optional = projectCustomField.stream().filter(item -> filed.equals(item.get("name"))).findAny();
            if (optional.isPresent()) {
                Map map = (Map) optional.get();
                result.put(filed, TBConf.getTaskFieldValue_First(taskCustomField, String.valueOf(map.get("id"))));
                result.put(filed + "_id", map.get("id")); // ppExt: 返回字段ID
            }
        }
        return result;
    }

    // 获取任务详情 fixme 提取到 tbservice
    private Map _getTaskFieldMap(String taskId, String... fieldNames) {
        // 查询任务详情
        Map rTask = tbClient.queryTaskDetail(taskId, null, null).get(0);
        // 查询项目任务ID
        List<Map> customField = tbClient.queryProjectCustomField(String.valueOf(rTask.get("projectId")), null);
        return __getTaskMap(rTask, customField, fieldNames);
    }

    // 匹配工作流名称, 获取ID
    @Deprecated
    String _getWorkFlowStatus(String projectId, String workFlowStatusName) {

        List<Map> customFlowStatus = tbClient.queryProjectCustomFlowStatus(projectId, UtilMap.map("q", workFlowStatusName));
        // 7.10 避免模糊匹配
        customFlowStatus = customFlowStatus.stream().filter(item -> workFlowStatusName.equals(UtilMap.getString(item, "name"))).collect(Collectors.toList());
        McException.assertAccessException(customFlowStatus.isEmpty(), "工作流名称未匹配");
        return String.valueOf(customFlowStatus.get(0).get("id"));
    }

    // 前置过滤 fixme 提取方法, 优化为单个查询, 处理保密模糊匹配
    @Deprecated
    List<String> _getWorkFlowStatusList(String projectId, String... workFlowStatusNames) {
        List<String> workFlowStatusList = new ArrayList<>();
        for (String name : workFlowStatusNames) {
            List<Map> customFlowStatus = tbClient.queryProjectCustomFlowStatus(projectId, UtilMap.map("q", name));
            // 7.10 避免模糊匹配
            customFlowStatus = customFlowStatus.stream().filter(item -> name.equals(UtilMap.getString(item, "name"))).collect(Collectors.toList());
            workFlowStatusList.addAll(customFlowStatus.stream().map(item -> UtilMap.getString(item, "id")).collect(Collectors.toList()));
        }
        return workFlowStatusList;
    }

    // 后置过滤 fixme 提取方法
    List<String> _getWorkFlowStatusList(String projectId, List<String> workFlowStatusNames) {
        //List<String> names = Arrays.stream(workFlowStatusNames).collect(Collectors.toList());
        List<Map> customFlowStatus = tbClient.queryProjectCustomFlowStatus(projectId, null);
        List<Map> workFlowStatusList = customFlowStatus.stream().filter(item -> workFlowStatusNames.contains(item.get("name"))).collect(Collectors.toList());
        return workFlowStatusList.stream().map(item -> UtilMap.getString(item, "id")).collect(Collectors.toList());
    }

    // 通过id获取工作流名称
    private String _getWorkFlowStatusName(String projectId, String tfsId) {
        List<Map> customFlowStatus = tbClient.queryProjectCustomFlowStatus(projectId, UtilMap.map("tfsIds", tfsId));
        return UtilMap.getString(customFlowStatus.get(0), "name");
    }

    /**
     * 获取知识库附件传递到宜搭
     * -
     * 1. 宜搭附件传递 downloadUrl 和 name 即可实现在线预览
     * 2. 知识库绑定没有文件后缀, 宜搭识别目前仅能点击下载跳转预览, 添加统一docx后缀, 文档会自行区分
     * -
     * ppExt: 获取任务字段需要传递字段名称以获取ID, 另外添加 .doc 只是重定向若是文件夹也不影响访问
     */
    List<Map> _getDocs(Map taskData, String fieldName) {
        Map rTask = UtilMap.getMap(taskData, "task");
        List<Map> customfields = UtilMap.getList(rTask, "customfields");
        String deliverable = String.valueOf(taskData.get(fieldName + "_id")); // 交付物任务字段ID
        List<Map> attachments = TBConf.getTaskFieldValue(customfields, deliverable).stream().map(item -> {
            // 7.8 兼容知识库版本: 无编辑权限, 复制失败 任务更新
            if (UtilMap.isBlankString(item, "metaString")) {
                return item;
            }
            Map link = (Map) JSON.parse(String.valueOf(item.get("metaString")));
            Map row = UtilMap.map("downloadUrl, name", link.get("url"), link.get("title") + ".docx");
            row.putAll(link); // 保留原数据作为知识库版本管理相关参数
            return row;
        }).collect(Collectors.toList());
        return attachments;
    }


    /// 获取自定义字段数据列表
    List<Map> _getCustomFieldList(Map taskData, String fieldName) {
        Map rTask = UtilMap.getMap(taskData, "task");
        List<Map> customfields = UtilMap.getList(rTask, "customfields");
        String deliverable = String.valueOf(taskData.get(fieldName + "_id")); // 自定义组件ID
        return TBConf.getTaskFieldValue(customfields, deliverable);
    }

    /**
     * 检查项check
     *
     * @param isTask 服务于任务自定义详情卡片, 否则为宜搭发起: 抛出错误信息
     */
    @Override
    public Map doCheck(String taskId, boolean isTask) {

        log.info("检查项check {}, isTask {}", taskId, isTask);
        Map taskData = _getTaskFieldMap(taskId, AWServer.TASK_CODE, AWServer.TASK_CHECK_LINK, AWServer.TASK_ROLE, AWServer.TASK_STAGE, AWServer.TASK_CHECK_STATUS, AWServer.TASK_PRODUCT, AWServer.TASK_PRODUCT_VERSION, AWServer.TASK_WAFER);
        Map rTask = UtilMap.getMap(taskData, "task");
        String pCode = UtilMap.getString(rTask, "projectId");
        if (AWServer.PROJECT_IGNORE_ID.contains(pCode)) {
            return UtilMap.map("result", "模板/复用项目");
        }
        String checkLink = UtilMap.getString(taskData, AWServer.TASK_CHECK_LINK);
        /// prd 7.17 预检项任务ID与当前任务ID不匹配, 判定为非法, 无需重新刷新
        if (isTask) {
            if (!(checkLink.contains("taskId=") && !checkLink.contains(taskId))) {
                /// prd 预检项持续维护需要重新匹配, 任务号和主数据判定为非法, 无需重新刷新
                if (StringUtils.isNotBlank(checkLink) && !"未配置预检项".equals(checkLink)) {
                    return UtilMap.map("result", "链接已加载");
                }
            }
        }
        // prd 非未完成任务若是未配置预检项状态, 忽略更新
        if (isTask && !_getWorkFlowStatusList(pCode, AWServer.WORKFLOW_INITIAL).contains(rTask.get("tfsId"))) {
            return UtilMap.map("result", "任务已执行");
        }
        // prd 7.17 creatorId 创建人修改为 执行人 executorId
        String executorId = UtilMap.getString(rTask, "executorId");
        String tCode = UtilMap.getString(taskData, AWServer.TASK_CODE);

        String resultTitle = "", resultUrl = "";
        List<Map> tList = null;
        Map rProject = null;
        if (StringUtils.isBlank(tCode)) {
            resultTitle = "无任务编码";
        } else {
            List<Map> pList = ydService.queryDataList_FormData(_matchFormUuid("PROJECT"), UtilMap.map("textField_lqxtykce", pCode));
            pList = pList.stream().filter(item -> pCode.equals(item.get("textField_lqxtykce"))).collect(Collectors.toList());
            if (pList.size() == 0) {
                resultTitle = "未匹配到项目主数据";
            } else {
                rProject = pList.get(0);
                String proType = UtilMap.getString(rProject, "textField_ltwcq7s6");
                Map searchCondition = UtilMap.map("selectField_lrncf4hk, radioField_lrnddfq6", tCode, "启用");
                /// prd 8.21 RTO 任务只需要查看 IC技术评审
                if (tCode.contains("RTO-")) {
                    searchCondition.put("selectField_lrnd3h0s", "IC技术检查表");
                }
                tList = ydService.queryFormData_all(YDParam.builder()
                        .formUuid(_matchFormUuid("CHECK"))
                        .searchFieldJson(JSON.toJSONString(searchCondition))
                        .build());
                // prd 多模板适配: [任务号 + 项目类型]
                tList = tList.stream().filter(item -> tCode.equals(item.get("selectField_lrncf4hk")) && UtilMap.getList(item, "multiSelectField_ltwjre9s").contains(proType)).collect(Collectors.toList());
                /// prd 8.21 RTO任务关联技术检查项自动化处理
                if (tCode.contains("RTO-")) {
                    // 按照技术领域区分, prd 0910 不改为多选逻辑, 需要查询全量数据匹配, 通过多条查询合并更可控
                    List<Map> rtoList = ydService.queryDataList_FormData(_matchFormUuid("RTO"), UtilMap.map("selectField_lrncf4hk", tCode));
                    rtoList = rtoList.stream().filter(item -> tCode.equals(item.get("selectField_lrncf4hk"))).collect(Collectors.toList());
                    for (Map rto : rtoList) {
                        Map searchMap = UtilMap.map("radioField_lrnddfq6, textField_8n5ib3y", "启用", UtilMap.getString(rto, "selectField_m0wbhut4"));
                        List<Map> rtoCheck = ydService.queryFormData_all(YDParam.builder()
                                .formUuid(_matchFormUuid("CHECK"))
                                .searchFieldJson(JSON.toJSONString(searchMap))
                                .build());
                        List<String> nodes = UtilMap.getList(rtoList.get(0), "multiSelectField_ltejinaz");
                        // 匹配评审点\项目类型\数据去重
                        for (Map rCheck : rtoCheck) {
                            if (nodes.contains(UtilMap.getString(rCheck, "textField_rfwx8lk")) &&
                                    UtilMap.getList(rCheck, "multiSelectField_ltwjre9s").contains(proType) &&
                                    !tList.stream().filter(row -> rCheck.get("textField_lrnd3h0r").equals(row.get("textField_lrnd3h0r"))).findAny().isPresent()) {
                                tList.add(rCheck);
                            }
                        }
                    }
                }
                if (tList.size() == 0) {
                    resultTitle = "未配置预检项";
                } else {
                    resultTitle = AWServer.TASK_CHECK_LINK_OK;
                    resultUrl = _matchFormUuid("DOMAIN") + ydConf.getAppType() + "/workbench/" + _matchFormUuid("RECORD") + "?taskId=" + taskId;
                }
            }
        }
        log.info("检查项结果, {}, {}", resultTitle, resultUrl);
        String result = resultTitle.equals(AWServer.TASK_CHECK_LINK_OK) ? resultUrl : resultTitle;
        if (isTask) {
            if (!result.equals(checkLink)) {
                Map body = TBConf.assembleCustomFieldName(AWServer.TASK_CHECK_LINK, result);
                tbClient.updateTaskCustomField(taskId, tbConf.getOperatorId(), body);
                // prd 7.17 技术检查项从未匹配到更新，刷新检查状态为空
                Map body2 = TBConf.assembleCustomFieldName(AWServer.TASK_CHECK_STATUS);
                tbClient.updateTaskCustomField(taskId, tbConf.getOperatorId(), body2);
//                _updateApproveField(taskId, new ArrayList<>(), resultUrl, resultTitle, "待提交", AWServer.TASK_CHECK_LINK);
            }
            // prd 未配置预检项更新为已检查, 避免完成任务完成触发必填校验 [ppExt 避免重复写入, 重复写入TB会重复记录日志
            if (!resultTitle.equals(AWServer.TASK_CHECK_LINK_OK) && !taskData.get(AWServer.TASK_CHECK_STATUS).equals("已检查")) {
                Map body = TBConf.assembleCustomFieldName(AWServer.TASK_CHECK_STATUS, "已检查");
                tbClient.updateTaskCustomField(taskId, tbConf.getOperatorId(), body);
            }
            return UtilMap.map("result", result);
        }
        // prd 7.17 添加执行人为空校验
        McException.assertAccessException(StringUtils.isBlank(executorId), "执行人不能为空！");
        McException.assertAccessException(!resultTitle.equals("技术检查确认"), resultTitle);

        Map formData = UtilMap.map("selectField_lqxuswzd, textField_lrndwu00, textField_lr3dlwsa, textField_lr3er4qb", tCode, pCode, taskId, executorId);
        // 项目信息
        formData.putAll(UtilMap.map("selectField_lqxuswze, textField_lrndwu07, textField_lqxuc9m4", UtilMap.getString(rProject, "textField_lrj7vnxb"), UtilMap.getString(rProject, "textareaField_lrj7vnxl"), UtilMap.getString(rProject, "textField_lqxtykcd")));
        // 任务信息 [TBManager 操作]
        Map<String, String> extra = (Map) tbClient.idMapQuery(executorId, "dingTalk-user", ddConf.getCorpId()).get(0).get("extra");
        // ppExt: TB映射钉钉信息仅返回 userId, 人员组件返回前端展示需要 name. 通过页面公式临时自动加载, 可通过TB接口或钉钉用户接口查询
        formData.putAll(UtilMap.map("textField_lrndwu09, textField_lrndwu0a, textField_lrndwu0b, employeeField_lrndwu0e, textField_lvbrueqs, textField_lvbrueqt, textField_m0yo7tsj", rTask.get("content"), taskData.get(AWServer.TASK_STAGE), taskData.get(AWServer.TASK_ROLE), Arrays.asList(UtilMap.map("value, name", extra.get("userId"), extra.get("userId"))), taskData.get(AWServer.TASK_PRODUCT), taskData.get(AWServer.TASK_PRODUCT_VERSION), taskData.get(AWServer.TASK_WAFER)));
        // prd 9.12 任务状态, 存量数据已更新, 新增数据自动带出任务状态 [ NA的任务也关联到, 新版本技术检查项查询全量数据, 已更新. fixme NA 任务也需要提交检查项, 否则不能关联 ]
        formData.put("textField_m0yo7tsk", _getWorkFlowStatusName(UtilMap.getString(rTask, "projectId"), UtilMap.getString(rTask, "tfsId")));
        // 返回检查项
        formData.put("tableField_lqxxgj4s", tList.stream().map(item -> {
            item.put("associationFormField_lrrnem5r", YDConf.associationForm(ydConf.getAppType(), _matchFormUuid("CHECK"), UtilMap.getString(item, "instanceId"), UtilMap.getString(item, "textField_lrnd3h0r"), "", false));
            return item;
        }).collect(Collectors.toList()));
        // prd 7.17 技术检查项需要匹配userId
        formData.put("userId", extra.get("userId"));
        return formData;
    }

    /**
     * 检查项回调
     */
    @Override
    public void checked(Map data) {

        log.info("检查项回调, {}", data);
        String title = UtilMap.getString(data, "title");
        Map body = TBConf.assembleCustomFieldName(AWServer.TASK_CHECK_STATUS, "已检查");
        tbClient.updateTaskCustomField(String.valueOf(data.get("taskId")), tbConf.getOperatorId(), body);
        // prd 7.31 复制的任务若是检查项已提交的任务, 也要触发 7.17 预检项任务ID与当前任务ID不匹配 逻辑
        String result = _matchFormUuid("DOMAIN") + ydConf.getAppType() + "/formDetail?formInstId=" + data.get("formInstId") + "&taskId=" + UtilMap.getString(data, "taskId");
        Map body2 = TBConf.assembleCustomFieldName(AWServer.TASK_CHECK_LINK, result);
        tbClient.updateTaskCustomField(String.valueOf(data.get("taskId")), tbConf.getOperatorId(), body2);
//        _updateApproveField(UtilMap.getString(data, "taskId"), new ArrayList<>(), result, title, "已提交", AWServer.TASK_CHECK_LINK);
    }

    /**
     * 同步项目主数据 [兼容新增]
     */
    @Synchronized
    @Override
    public Map syncProject(String projectCode) {

        // 新增场景下, 提供实例ID回写TB项目编号
        Map formData = null;

        // 查询项目主数据 [艾为应用]
        YDParam ydParam = YDParam.builder()
                .appType("APP_QBWQITQBSPJNYTUTNPDK")
                .systemToken("8F966HB12J27MQJM6V4IQDYHYTPA2G4GTZGCLN1")
                .formUuid("FORM-Q4A664A1V158R81L662N9507PLND288T7O6ELN")
                .build();
        if (StringUtils.isNotBlank(projectCode)) {
            ydParam.setSearchFieldJson(JSON.toJSONString(UtilMap.map("textField_le6o88w0", projectCode)));
        }
        // prd 客户系统主数据, 是全量定时从mdm更新, 因此不能增量
        List<Map> mapBaseList = ydService.queryFormData_all(ydParam);
        if (StringUtils.isNotBlank(projectCode)) {
            mapBaseList = mapBaseList.stream().filter(item -> projectCode.equals(item.get("textField_le6o88w0"))).collect(Collectors.toList());
        }
        log.info("项目主数据, {}", mapBaseList.size());
        McException.assertAccessException(mapBaseList.isEmpty(), "未匹配项目主数据");
        // 查询项目角色
        YDParam ydParam1 = YDParam.builder()
                .formUuid(_matchFormUuid("ROLE"))
                .build();
        List<Map> mapRole = (List<Map>) ydClient.queryData(ydParam1, YDConf.FORM_QUERY.retrieve_search_form).getData();
        // prd 5.14 C类项目成员同步PDT成员主数据
        ydParam1.setFormUuid(_matchFormUuid("ROLE_PDT"));
        List<Map> rolePDT = (List<Map>) ydClient.queryData(ydParam1, YDConf.FORM_QUERY.retrieve_search_form).getData();

        // 匹配数据组件ID
        for (Map baseFormData : mapBaseList) {
            // 项目主数据
            formData = new HashMap();
            formData.put("textField_lqxtykcd", baseFormData.get("textField_lo2c1f0l"));      // 项目名称
            formData.put("textField_lrj7vnxb", baseFormData.get("textField_le6o88w0"));      // 项目号
            formData.put("textField_lrj7vnxc", baseFormData.get("textField_le6o88w1"));      // 项目简称
            formData.put("textareaField_lrj7vnxl", baseFormData.get("textField_le6o88w2"));  // 项目描述
            formData.put("textField_lrj7vnxf", baseFormData.get("formInstanceId"));          // 项目实例ID
            formData.put("textField_ltsdsti6", baseFormData.get("textField_libg2ea8"));      // PDT (项目分组)
            formData.put("textField_ltsdsti7", baseFormData.get("textField_llouhiyf"));      // 项目状态
            formData.put("textField_ltsdsti8", baseFormData.get("textField_lo2c1f0r"));      // mPDT
            formData.put("textField_ltsdsti9", baseFormData.get("textField_lki3egzn"));      // Charter子类
            formData.put("textField_ltwcq7s5", baseFormData.get("textField_lki3egzm"));      // Charter
            formData.put("textField_ltwcq7s6", baseFormData.get("textField_lnjqpeie"));      // 项目类型 (TB项目模板)
            formData.put("textField_lwj1r7n6", baseFormData.get("textField_lqnf2z2z"));      // 项目重要性分级
            formData.put("textField_lwj1r7n7", baseFormData.get("textField_lnjqpeif"));      // 项目等级
            List<Map> details = new ArrayList<>();
            List<Map> roleMap = mapRole;
            Map projectMembers = baseFormData;
            // 项目角色详情 [prd 5.14 C类项目成员同步PDT成员主数据]
            if ("Charter开发项目".equals(formData.get("textField_ltwcq7s6"))) {
                roleMap = rolePDT;
                String charter = String.valueOf(formData.get("textField_ltsdsti6"));
                YDParam ydParam2 = YDParam.builder()
                        .appType("APP_LH2TJTIT3EWHCG084TGL")
                        .systemToken("QYC66KA1F4ZG5RQV8HR9WBHYW03S3CQAJKDQLZ1")
                        .formUuid("FORM-90E23DE1BBEB43759ED9517F98BA4938P61C")
                        .searchFieldJson(JSON.toJSONString(UtilMap.map("textField_lr0dvhqh", charter)))
                        .build();
                List<Map> listPDT = (List<Map>) ydClient.queryData(ydParam2, YDConf.FORM_QUERY.retrieve_search_form).getData();
                listPDT = listPDT.stream().filter(item -> charter.equals(((Map) item.get("formData")).get("textField_lr0dvhqh"))).collect(Collectors.toList());
                if (listPDT.size() == 0) {
                    continue;
                }
                projectMembers = (Map) listPDT.get(0).get("formData");
            }
            for (Map roleData : roleMap) {
                Map roleFormData = (Map) roleData.get("formData");
                // 创建详情的角色名称和成员组件Map
                Object roleMember = projectMembers.get(roleFormData.get("textField_lr7bgi76"));
                String roleName = UtilMap.getString(roleFormData, "textField_lqxu439g");
                if (roleMember != null && roleName != null) {
                    // prd 提交代表标识, 用于同步项目角色
                    details.add(UtilMap.map("employeeField_lqxtykch, selectField_lqxu6bgf, radioField_lu8li5ie", roleMember, roleName, roleFormData.get("radioField_lu8li5ie")));
                    // prd 添加项目经理, 用于数据权限控制
                    if (AWServer.PROJECT_PM_NAME.equals(roleName)) {
                        formData.put("employeeField_ltzn872j", roleMember);
                    }
                }
            }
            formData.put("tableField_lqxtykcf", details);
            // 通过项目号获取项目是否已存在
            List<Map> projectMaps = ydService.queryDataList_FormData(_matchFormUuid("PROJECT"), UtilMap.map("textField_lrj7vnxb", baseFormData.get("textField_le6o88w0")));
            projectMaps = projectMaps.stream().filter(sub -> baseFormData.get("textField_le6o88w0").equals(sub.get("textField_lrj7vnxb"))).collect(Collectors.toList());
            YDParam ydParam2 = YDParam.builder()
                    .formUuid(_matchFormUuid("PROJECT"))
                    .formDataJson(JSON.toJSONString(formData))
                    .updateFormDataJson(JSON.toJSONString(formData))
                    .useLatestVersion(true)
                    .build();
            if (projectMaps.size() > 0) {
                /// 7.18 项目状态不一致执行概览字段更新, 避免tb重复更新造成操作记录
                if (!formData.get("textField_ltsdsti7").equals(projectMaps.get(0).get("textField_ltsdsti7"))) {
                    formData.put("src_state", formData.get("textField_ltsdsti7"));
                }
                /// 8.21 项目信息\项目重要性分级, 也添加差异同步, 避免新增字段历史项目不同步问题
                if (!formData.get("textareaField_lrj7vnxl").equals(projectMaps.get(0).get("textareaField_lrj7vnxl"))) {
                    formData.put("src_desc", formData.get("textareaField_lrj7vnxl"));
                }
                if (!formData.get("textField_lwj1r7n6").equals(projectMaps.get(0).get("textField_lwj1r7n6"))) {
                    formData.put("src_level", formData.get("textField_lwj1r7n6"));
                }

                ydParam2.setFormInstanceId(String.valueOf(projectMaps.get(0).get("instanceId")));
                ydClient.operateData(ydParam2, YDConf.FORM_OPERATION.update); // 主表为增量更新
                // 保留tb项目号, 避免冲重复通过模板参加项目 [供新增用]
                formData.put("textField_lqxtykce", UtilMap.getString(projectMaps.get(0), "textField_lqxtykce"));
                formData.put("formInstanceId", projectMaps.get(0).get("instanceId"));
            } else {
                String formInstId = (String) ydClient.operateData(ydParam2, YDConf.FORM_OPERATION.create);
                formData.put("formInstanceId", formInstId);
            }
            // 全量兼容: tb项目成员更新 [兼容存量创建项目, 被删除后执行异常]
            String projectId = UtilMap.getString(formData, "textField_lqxtykce");
            if (StringUtils.isNotBlank(projectId) && StringUtils.isBlank(projectCode)) {
                try {
                    _syncProjectRole(formData, projectId, false);
                    _assembleBreakPermission(formData);
                } catch (Exception e) {
                    // 记录错误信息
                    e.printStackTrace();
                }
            }
            log.info("同步项目主数据, code = {}, id = {}", projectCode, projectCode);
        }
        return formData;
    }

    @Autowired
    private TBConf tbConf;

    /**
     * 通过模板创建项目 [ppExt: 过滤宜搭项目档案\TB项目, 避免重复创建, 若判定为更新下执行项目成员更新]
     */
    @Override
    @Synchronized
    public void createProject(String projectCode, String templateId) {

        log.info("通过模板创建项目, {}, {}", projectCode, templateId);
        // 虽然先创建TB, 可以一次性同步项目数据, 但避免执行异常, 因此成功创建TB项目再回写
        Map formData = syncProject(projectCode);
        String projectId = UtilMap.getString(formData, "textField_lqxtykce");
        log.info("项目主数据, {}, {}", projectId, formData);
        boolean isCreate = StringUtils.isBlank(projectId);
        if (isCreate) {
            // prd 多模板适配: [templateId 为空, 触发项目类型匹配]
            if (StringUtils.isBlank(templateId)) {
                List<Map> dataList = ydService.queryDataList_FormData(_matchFormUuid("TEMPLATE"), null);
                Optional optional = dataList.stream().filter(item -> item.get("selectField_ltwcsr8w").equals(formData.get("textField_ltwcq7s6"))).findAny();
                McException.assertAccessException(!optional.isPresent(), projectCode + ": TB项目模板未维护!");
                templateId = UtilMap.getString((Map) optional.get(), "textField_ltwcybz1");
                McException.assertAccessException(templateId.length() <= 1, projectCode + ": TB项目模板未匹配!");
            }
            // 通过模板创建项目, 创建项目并更新项目TB项目ID
            Map result = tbClient.projectCreateWithTemplate(projectCode, templateId, tbConf.getOperatorId());
            projectId = UtilMap.getString(result, "id");
            ydClient.operateData(YDParam.builder()
                    .formInstanceId(UtilMap.getString(formData, "formInstanceId"))
                    .updateFormDataJson(JSON.toJSONString(UtilMap.map("textField_lqxtykce", projectId)))
                    .build(), YDConf.FORM_OPERATION.update);
            log.info("TB项目号更新, {}, {}", projectCode, UtilMap.getString(formData, "formInstanceId"), projectId);
        }
        _syncProjectRole(formData, projectId, isCreate);
    }

    /**
     * 项目主数据增量更新
     */
    @Override
    public void updateProject(String projectCode) {
        Map formData = syncProject(projectCode);
        log.info("项目主数据增量更新, {}", formData);
        String projectId = UtilMap.getString(formData, "textField_lqxtykce");
        if (StringUtils.isNotBlank(projectId)) { // 屏蔽未创建项目, 出现新增
            _syncProjectRole(formData, projectId, false);
            _assembleBreakPermission(formData);
        }
    }

    // 同步TB项目成员/项目分组/项目概览
    void _syncProjectRole(Map formData, String projectId, boolean isCreate) {

        // prd TB项目成员, 项目经理设置为管理者
        List<String> pmUserId = new ArrayList<>();
        List<String> roleIds = new ArrayList<>();
        List<String> representativeIds = new ArrayList<>();

        String representative = _getProjectRoleId(projectId, "项目各代表");
        // prd 6.26 L类\C类项目使用 项目各代表_L, TB配置拥有邀请成员权限
        if (Arrays.asList("生命周期维护项目", "Charter开发项目").contains(formData.get("textField_ltwcq7s6"))) {
            representative = _getProjectRoleId(projectId, "项目各代表_L");
        }
        String pmRoleId = _getProjectRoleId(projectId, AWServer.PROJECT_PM_ROLE);
        String staffRoleId = _getProjectRoleId(projectId, "项目成员");

        List<Map> details = (List<Map>) formData.get("tableField_lqxtykcf");
        for (Map row : details) {
            // 同步项目主数据, 会重新匹配项目成员, 因此无需 employeeField_xxx_id 取值
            List<String> userIds = (List<String>) UtilMap.getList(row, "employeeField_lqxtykch");
            if (userIds.isEmpty()) {
                continue;
            }
            String roleName = UtilMap.getString(row, "selectField_lqxu6bgf");
            // prd 7.3 C类项目管理员权限, 分配为产品代表
            String tbRole = "Charter开发项目".equals(formData.get("textField_ltwcq7s6")) ? "产品代表" : AWServer.PROJECT_PM_NAME;
            if (tbRole.equals(roleName)) {
                pmUserId.addAll(userIds);
            }
            // prd 项目各代表, 可独立N/A任务, 添加角色控制权限
            if ("是".equals(row.get("radioField_lu8li5ie"))) {
                representativeIds.addAll(userIds);
            }
            roleIds.addAll(userIds);
        }
        // 钉钉人员ID转TB人员ID
        roleIds = _convertUserId(roleIds, false);
        tbClient.createProjectMember(projectId, roleIds, tbConf.getOperatorId());
        if (!representativeIds.isEmpty()) {
            tbClient.updateProjectMember(_convertUserId(representativeIds, false), Arrays.asList(staffRoleId, representative), projectId);
        }
        // prd 不做删除, 离职自动退出, 避免临时交接不可见的问题. 但需要移除项目经理权限
        List<Map> roleList = tbClient.queryProjectMember(projectId, UtilMap.map("projectRoleId", pmRoleId));
        for (Map role : roleList) {
            List<String> tRoleIds = UtilMap.getList(role, "roleIds");
            tRoleIds.remove(pmRoleId);
            if (tRoleIds.isEmpty()) {
                tRoleIds.add(staffRoleId);
            }
            tbClient.updateProjectMember(Arrays.asList(UtilMap.getString(role, "userId")), tRoleIds, projectId);
        }
        // 指定项目经理为编辑权限
        if (!pmUserId.isEmpty()) {
            tbClient.updateProjectMember(_convertUserId(pmUserId, false), Arrays.asList(pmRoleId), projectId);
        }
        // 同步项目分组 [prd 主数据建立产品与分组对照表，绑定末级分类接口，若未匹配则不添加]
        String groupName = UtilMap.getString(formData, "textField_ltsdsti6");
        if (StringUtils.isNotBlank(groupName)) {
            List<Map> tagList = tbClient.queryTagList(UtilMap.map("q", groupName));
            if (tagList.size() > 0) {
                /// 更新项目分组, 不会保留原分组, 需要兼容
                List<String> tags = tbClient.queryProjectTag(projectId, tbConf.getOperatorId());
                tags.add(UtilMap.getString(tagList.get(0), "id"));
                tbClient.updateProjectTag(projectId, tags, tbConf.getOperatorId());
            }
        }
        // prd 人员变更, 自动同步更新执行人: 仅触发存在执行人 & 未完成场景 [5.10 新增项目不执行, TB任务号宜搭回写有延迟, 避免异常]
        if (!isCreate) {
            updateProjectRole(projectId, Arrays.asList("全部", "自动更新"), "", "");
        }
        // prd 5.23 更新项目概览信息 8.21 项目信息\项目重要性分级, 也添加差异同步, 避免新增字段历史项目不同步问题
        if (UtilMap.isNotBlankString(formData, "src_desc")) {
            tbClient.updateProjectStatusField(projectId, tbConf.getOperatorId(), TBConf.assembleCustomFieldName("项目描述", UtilMap.getString(formData, "textareaField_lrj7vnxl")));
        }
        if (UtilMap.isNotBlankString(formData, "src_level")) {
            tbClient.updateProjectStatusField(projectId, tbConf.getOperatorId(), TBConf.assembleCustomFieldName("项目重要等级", UtilMap.getString(formData, "textField_lwj1r7n6")));
        }
        // 项目添加到项目集
        List<Map> groupList = tbClient.queryProgramList_all();
        Optional optional = groupList.stream().filter(item -> groupName.equals(item.get("name"))).findAny();
        if (optional.isPresent()) {
            Map item = (Map) optional.get(); // 权限
            tbClient.upsertProgramProject(UtilMap.getString(item, "id"), Arrays.asList(projectId), UtilMap.getString(item, "creatorId"));
        }
        /// 7.18 项目状态不一致执行概览字段更新, 避免tb重复更新造成操作记录
        if (UtilMap.isNotBlankString(formData, "src_state")) {
            tbClient.updateProjectStatusField(projectId, tbConf.getOperatorId(), TBConf.assembleCustomFieldName("状态", UtilMap.getString(formData, "textField_ltsdsti7")));
        }
        log.info("TB项目信息, {}, {}", projectId, roleIds.size());
    }

    // fixme 提取方法, 参考 getProjectCFID 实现
    private String _getProjectRoleId(String projectId, String roleName) {
        List<Map> roles = tbClient.queryProjectRoles(projectId);
        Optional optional = roles.stream().filter(item -> roleName.equals(item.get("name"))).findAny();
        McException.assertAccessException(!optional.isPresent(), roleName + ": 项目角色不存在");
        return UtilMap.getString((Map) optional.get(), "id");
    }

    /**
     * 分配项目角色 prd 若是一人直接指定, 多人情况下忽略
     */
    @Synchronized
    @Override
    public void updateProjectRole(String projectId, List<String> trNode, String production, String version) {

        // 项目档案 [tb项目号查询, 避免模糊匹配]
        List<Map> pList = ydService.queryDataList_FormData(_matchFormUuid("PROJECT"), UtilMap.map("textField_lqxtykce", projectId));
        McException.assertAccessException(pList.isEmpty(), "未匹配到项目主数据");
        List<Map> rList = (List<Map>) pList.get(0).get("tableField_lqxtykcf");

        log.info("分配项目角色 projectId = {}, 项目数量 = {}, 角色列表 = {}", projectId, pList.size(), rList);
        // 任务编码字段ID
        List<Map> customField = tbClient.queryProjectCustomField(projectId, null);
        // prd 非未完成任务, 不执行人员匹配更新
        List<String> workFlowStatusList = _getWorkFlowStatusList(projectId, AWServer.WORKFLOW_INITIAL);

        //List<Map> taskList = tbClient.queryProjectTaskList(projectId, UtilMap.map("pageSize", 10), null);
        tbClient.queryProjectTaskList(projectId, null, taskList -> {
            for (Map task : taskList) {
                if (!workFlowStatusList.contains(task.get("tfsId"))) {
                    continue;
                }
                // prd 0920 下发不阻断，没有执行人也更新，已下达 [ 存量任务兼容未添加字段 ]
                try {
                    Map body = TBConf.assembleCustomFieldName(AWServer.TASK_TRANSMIT, "已下达");
                    tbClient.updateTaskCustomField(UtilMap.getString(task, "id"), tbConf.getOperatorId(), body);
                } catch (McException e) {
                    log.error(e.getMessage(), e);
                }
                // 获取资源名称对应的项目角色
                Optional optional = customField.stream().filter(item -> AWServer.TASK_ROLE.equals(item.get("name"))).findAny();
                if (!optional.isPresent()) {
                    continue;
                }
                List<Map> customfields = (List<Map>) task.get("customfields");
                // prd 按照tr节点分配成员, 若是含有全部则忽略
                if (!trNode.contains("全部")) {
                    Optional optional2 = customField.stream().filter(item -> AWServer.TASK_STAGE.equals(item.get("name"))).findAny();
                    if (!optional2.isPresent()) {
                        continue;
                    }
                    // prd 传递无TR评审节点值, 匹配空值, 作为分配条件
                    String trName = TBConf.getTaskFieldValue_First(customfields, UtilMap.getString((Map) optional2.get(), "id"));
                    if (StringUtils.isBlank(trName)) {
                        if (!trNode.contains(AWServer.TASK_STAGE_BLANK)) {
                            continue;
                        }
                    } else {
                        if (!trNode.contains(trName)) {
                            continue;
                        }
                    }
                    /// prd: 08.01 TR5 ~  TR6 有产品版本，为空则匹配所选 TR 下全部任务，否则按照所选产品、版本下达
                    if (UtilString.isNotBlankCompatNull(production)) {
                        Optional optional3 = customField.stream().filter(item -> AWServer.TASK_PRODUCT.equals(item.get("name"))).findAny();
                        if (!optional3.isPresent()) {
                            continue;
                        }
                    }
                    if (UtilString.isNotBlankCompatNull(version)) {
                        Optional optional3 = customField.stream().filter(item -> AWServer.TASK_PRODUCT_VERSION.equals(item.get("name"))).findAny();
                        if (!optional3.isPresent()) {
                            continue;
                        }
                    }
                    log.info("tr节点控制, {}, {}", trName, trNode);
                }
                // prd 人员变更, 自动同步更新执行人: 仅触发存在执行人 & 未完成场景
                if (trNode.contains("自动更新")) {
                    if (StringUtils.isBlank(UtilMap.getString(task, "executorId"))) {
                        continue;
                    }
                }
                String roleName = TBConf.getTaskFieldValue_First(customfields, UtilMap.getString((Map) optional.get(), "id"));
                // 获取角色在项目主数据对应成员
                optional = rList.stream().filter(item -> roleName.equals(item.get("selectField_lqxu6bgf"))).findAny();
                if (!optional.isPresent()) {
                    continue;
                }
                log.info("分配项目角色 taskId = {}, 资源名称 = {}, 角色信息 = {}", UtilMap.getString(task, "id"), roleName, optional.get());
                List<String> roleIds = (List<String>) UtilMap.getList((Map) optional.get(), "employeeField_lqxtykch_id");
                if (roleIds.size() == 1) {
                    String tbUserId = _convertUserId(roleIds.get(0), false);
                    if (StringUtils.isNotBlank(tbUserId)) {
                        tbClient.updateTaskExecutor(UtilMap.getString(task, "id"), tbConf.getOperatorId(), tbUserId, false, false);
                    }
                }
                // prd 6.17 任务分配：资源匹配多人时，取值第一人作为执行人，其他人员作为参与者
                if (roleIds.size() > 1) {
                    String executorId = UtilMap.getString(task, "executorId");
                    List<String> tbUserIds = _convertUserId(roleIds, false);
                    // prd 多人情况避免, 手动指定后被覆盖. 判定当前任务执行人在角色范围内则不支持更新
                    if (StringUtils.isNotBlank(executorId) && tbUserIds.contains(executorId)) {
                        log.info("多人情况跳过, {}", tbUserIds.contains(executorId));
                        continue;
                    }
                    if (tbUserIds.size() > 0) {
                        tbClient.updateTaskExecutor(UtilMap.getString(task, "id"), tbConf.getOperatorId(), tbUserIds.get(0), false, false);
                    }
                    tbClient.updateTaskInvolveMembers(UtilMap.getString(task, "id"), tbConf.getOperatorId(), tbUserIds, null, false, false);
                }
            }
            return true;
        });
    }

    /**
     * 项目迁移: 删除依赖项
     */
    @Synchronized
    @Override
    public void removeDependencies(String projectId, List<String> trNode) {
        // 任务编码字段ID
        List<Map> customField = tbClient.queryProjectCustomField(projectId, null);
        tbClient.queryProjectTaskList(projectId, null, taskList -> {
            for (Map task : taskList) {

                Optional optional = customField.stream().filter(item -> AWServer.TASK_STAGE.equals(item.get("name"))).findAny();
                if (!optional.isPresent()) {
                    continue;
                }
                List<Map> customfields = (List<Map>) task.get("customfields");
                String trName = TBConf.getTaskFieldValue_First(customfields, UtilMap.getString((Map) optional.get(), "id"));
                // prd 传递无TR评审节点值, 匹配空值, 作为删除条件
                if (trNode.contains(trName) || (StringUtils.isBlank(trName) && trNode.contains(AWServer.TASK_STAGE_BLANK))) {
                    String taskId = UtilMap.getString(task, "id");
                    List<Map> dependencies = tbClient.queryTaskDependency(taskId, null);
                    log.info("taskID = {}, trNode, {}, 依赖数量: {}", taskId, trName, dependencies.size());
                    for (Map dependency : dependencies) {
                        tbClient.removeTaskDependency(UtilMap.getString(dependency, "id"), tbConf.getOperatorId());
                    }
                }
            }
            return true;
        });
    }

    // TB与宜搭userId转换 fixme 提取 tbService [ ppExt: 若TB未启用人员返回为空 ]
    private String _convertUserId(String userId, boolean isTBID) {
        List<Map> tbMap = tbClient.idMapQuery(userId, isTBID);
        // 过滤未匹配人员信息 [ppExt: TB人员未匹配, 不执行修改也无报错]
        if (tbMap.isEmpty()) {
            log.warn("TB与宜搭userId转换为空, userId = {}, tbID = {}", userId, isTBID);
            return "";
        }
        if (!isTBID) {
            return UtilMap.getString(tbMap.get(0), "tbId");
        } else {
            Map extra = UtilMap.getMap(tbMap.get(0), "extra");
            return UtilMap.getString(extra, "userId");
        }
    }

    // TB与宜搭userId转换 fixme 提取 tbService [ ppExt: 若TB未启用人员返回为空 ]
    private List<String> _convertUserId(List<String> userIds, boolean isTBID) {
        return userIds.stream().distinct()
                .map(id -> _convertUserId(id, isTBID))
                .filter(id -> StringUtils.isNotBlank(id))
                .collect(Collectors.toList());
    }

    /**
     * 同步crm基线
     */
    @Override
    public void syncBaseLineForCRM(String projectId) {
        String C_TR5_03_13 = "TR5-03-13";
        String C_TR6_01_11 = "TR6-01-11";

        // prd 推CRM的逻辑， 完成时间晚于2024/3/29的就更新，否则不更新
        Date dlDate = UtilDateTime.parseDateTime("2024-03-29 23:59:59");

        // 7. 产品型号 (Name) : 以上计划时间对应的产品型号 / 取产品型号字段
        List<Map> customField = tbClient.queryProjectCustomField(projectId, null);

        // 基线同步需求
        List<Map> baseLines = tbClient.queryGanttBaselineList(projectId, null).stream()
                .filter(item -> UtilMap.getString(item, "title").toUpperCase().contains("PDCP")).collect(Collectors.toList());
        log.info("PDCP基线, {}, {}", projectId, baseLines.size());
        List<Map> baseLineTaskList = new ArrayList();
        if (baseLines.size() > 0) {
            // prd 若存在多个同名, 取最新基线 [返回值创建时间升序]
            tbClient.queryGanttBaselineTask(UtilMap.getString(baseLines.get(baseLines.size() - 1), "id"), null, taskList -> {
                baseLineTaskList.addAll(taskList);
                return true;
            });
        }
        log.info("基线任务, {}", baseLineTaskList.size());

        // 最新同步需求
        Map<String, String> proData = getProjectCFID(projectId, Arrays.asList(AWServer.TASK_CODE, AWServer.TASK_PRODUCT));
        String tql = "cf:" + proData.get(AWServer.TASK_CODE) + " = " + C_TR5_03_13 + " AND (cf:" + proData.get(AWServer.TASK_PRODUCT) + " != null)" + " ORDER BY startDate DESC";
        List<Map> taskList = tbClient.queryProjectTaskList(projectId, UtilMap.map("q", tql), null);
        log.info("项目计划: {}, {}", C_TR5_03_13, taskList.size());

        List<Map> customFlowStatus = tbClient.queryProjectCustomFlowStatus(projectId, null);
        List<String> nastatus = new ArrayList<>();
        for (Map flowStatus : customFlowStatus) {
            if ("NA".equals(flowStatus.get("name").toString()) || "N/A".equals(flowStatus.get("name").toString())) {
                nastatus.add((String) flowStatus.get("id"));
            }
        }
        log.info("nastatus, {}", nastatus);

        List<Map> dataList = new ArrayList<>();
        for (Map task : taskList) {
            String name = _getTaskFieldMap(task, customField, AWServer.TASK_PRODUCT).get(AWServer.TASK_PRODUCT);
            // 多料号兼容
            if (dataList.stream().filter(item -> name.equals(item.get("Name"))).findAny().isPresent()) {
                continue;
            }
            // 任务状态为NA不取数据
            String tfsId = (String) task.get("tfsId");
            if (nastatus.contains(tfsId)) {
                log.info("任务状态为NA，跳过！, {}", tfsId);
                continue;
            }
            Map tData = UtilMap.map("Name", name);
            // 2. 预计α推广 (AlphaDate): 最新项目计划（非基线）里的ADCPα任务对应的计划完成时间 / TR5-03-13任务号 取最新版本
            String date = UtilMap.getString(task, "dueDate");
            if (UtilString.isNotBlankCompatNull(date)) {
                Date tDate = UtilDateTime.parse(date, UtilDateTime.DATE_MSEL_ISO);
                if (tDate.after(dlDate)) {
                    tData.put("AlphaDate", UtilDateTime.formatDate(tDate));
                }
            }
            // 3. α推广 (AlphaDate2): 【ADCPα】对应的实际完成时间 / TR5-03-13任务号 取最新版本 [ prd 2024-09-10实际完成时间不传 ]
//            date = UtilMap.getString(task, "accomplishTime");
//            if (UtilString.isNotBlankCompatNull(date)) {
//                Date tDate = UtilDateTime.parse(date, UtilDateTime.DATE_MSEL_ISO);
//                if (tDate.after(dlDate)) {
//                    tData.put("AlphaDate2", UtilDateTime.formatDate(tDate));
//                }
//            }
            // 1. α推广基线 (AlphaBaseline): PDCP basseline 基线下的ADCPα任务的计划完成时间 / PDCP basseline 基线 TR5-03-13任务号
            Optional optional = baseLineTaskList.stream().filter(item -> task.get("id").equals(item.get("id"))).findAny();
            if (optional.isPresent()) {
                date = UtilMap.getString((Map) optional.get(), "planDueDate");
                if (StringUtils.isNotBlank(date)) {
                    Date tDate = UtilDateTime.parse(date, UtilDateTime.DATE_MSEL_ISO);
                    if (tDate.after(dlDate)) {
                        tData.put("AlphaBaseline", UtilDateTime.formatDate(tDate));
                    }
                }
            }
            if (tData.keySet().size() >= 2) {
                dataList.add(tData);
                _pushCRM(projectId, tData);
            }
        }

        tql = "cf:" + proData.get(AWServer.TASK_CODE) + " = " + C_TR6_01_11 + " AND (cf:" + proData.get(AWServer.TASK_PRODUCT) + " != null)" + " ORDER BY startDate ASC";
        taskList = tbClient.queryProjectTaskList(projectId, UtilMap.map("q", tql), null);
        log.info("项目计划: {}, {}", C_TR6_01_11, taskList.size());

        dataList = new ArrayList<>();
        for (Map task : taskList) {
            String name = _getTaskFieldMap(task, customField, AWServer.TASK_PRODUCT).get(AWServer.TASK_PRODUCT);
            // 多料号兼容
            if (dataList.stream().filter(item -> name.equals(item.get("Name"))).findAny().isPresent()) {
                continue;
            }
            log.info("任务tfsId: {}", task.get("tfsId"));
            String tfsId = (String) task.get("tfsId");

            //任务状态为NA不取数据
            if (nastatus.contains(tfsId)) {
                log.info("任务状态为NA，跳过！");
                continue;
            }
            Map tData = UtilMap.map("Name", name);
            // 5. 预计正式发布 (AgentSampleDate): 最新项目计划（非基线），取最早那个产品版本的计划完成时间 / TR6-01-11任务号 最早版本
            String date = UtilMap.getString(task, "dueDate");
            if (UtilString.isNotBlankCompatNull(date)) {
                Date tDate = UtilDateTime.parse(date, UtilDateTime.DATE_MSEL_ISO);
                if (tDate.after(dlDate)) {
                    tData.put("AgentSampleDate", UtilDateTime.formatDate(tDate));
                }
            }
            // 6. 正式发布 (ProductSendDate): 【ADCP】对应的取最早那个产品版本的实际完成时间 / TR6-01-11任务号 最早版本 [ prd 2024-09-10实际完成时间不传 ]
//            date = UtilMap.getString(task, "accomplishTime");
//            if (UtilString.isNotBlankCompatNull(date)) {
//                Date tDate = UtilDateTime.parse(date, UtilDateTime.DATE_MSEL_ISO);
//                if (tDate.after(dlDate)) {
//                    tData.put("ProductSendDate", UtilDateTime.formatDate(tDate));
//                }
//            }
            // 4. 正式发布基线 (ReleaseBaseline): 取PDCPbasseline基线里的ADCP的计划完成时间，取最早那个产品版本的计划完成时间 / PDCP basseline 基线 TR6-01-11任务号
            Optional optional = baseLineTaskList.stream().filter(item -> task.get("id").equals(item.get("id"))).findAny();
            if (optional.isPresent()) {
                date = UtilMap.getString((Map) optional.get(), "planDueDate");
                if (StringUtils.isNotBlank(date)) {
                    Date tDate = UtilDateTime.parse(date, UtilDateTime.DATE_MSEL_ISO);
                    if (tDate.after(dlDate)) {
                        tData.put("ReleaseBaseline", UtilDateTime.formatDate(tDate));
                    }
                }
            }
            if (tData.keySet().size() >= 2) {
                dataList.add(tData);
                _pushCRM(projectId, tData);
            }
        }
    }

    // 推送crm: prd 单次推送必须为相同料号
    void _pushCRM(String projectId, Map record) {
        log.info("推送crm, {}", record);
        //Map param = UtilMap.map("username, password, client_id, client_secret, grant_type", "interface@awinic.com.cn.uat", "welcome12", "3MVG959Nd8JMmavT2IGqAtf_hIbxepsElGbOpno6AO8KdQJSpSNqY9bnLRU2exuAEh3qIXb1oTn98S9h0WWZk", "3C9FC7427866D69586964F65A16D288EBBB1544335DD9FF3A03DE21DF14A7C6A", "password");
        Map param = UtilMap.map("username, password, client_id, client_secret, grant_type", "interface@awinic.com.cn", "welcome12", "3MVG9d8..z.hDcPKSaWop76C8GRQarYhn0LpBnP_U2S3VJaT8a6l05e2pDkdk5oE4MInQYwyLMpqWM7bLDzqm", "26A90098BDBE8C356817938986472730BAE5D9536DA9096AC7C45A4973867100", "password");
        //String rsp = UtilHttp.doPost("https://test.salesforce.com/services/oauth2/token", null, param, UtilMap.empty());
        String rsp = UtilHttp.doPost("https://login.salesforce.com/services/oauth2/token", null, param, UtilMap.empty());
        Map result = (Map) JSON.parse(rsp);
        Map header = UtilMap.map("Authorization", "OAuth " + result.get("access_token"));
        rsp = UtilHttp.doPost(UtilMap.getString(result, "instance_url") + "/services/apexrest/TBProductStageTimeRest", header, null, Arrays.asList(record), null);
        ydClient.operateData(YDParam.builder()
                .formUuid(_matchFormUuid("CRM_LOG"))
                .formDataJson(JSON.toJSONString(UtilMap.map("textareaField_lu8kinep, textareaField_lu8kineu, textField_lu8kinew", Arrays.asList(record), rsp, projectId)))
                .build(), YDConf.FORM_OPERATION.create);
    }

    /**
     * 全量同步crm基线
     */
    @Override
    public void syncBaseLineForCRM() {

        // prd 同步CRM: 项目状态为执行 & TB项目号不为空
        YDParam ydParam = YDParam.builder()
                .formUuid(_matchFormUuid("PROJECT"))
                .searchFieldJson(JSON.toJSONString(UtilMap.map("textField_ltsdsti7", "执行")))
                .build();
        List<Map> proList = ydService.queryFormData_all(ydParam);
        log.info("推送总计数据数量：" + proList.size());
        int num = 0;
        for (Map pro : proList) {
            String projectId = UtilMap.getString(pro, "textField_lqxtykce");
            if (StringUtils.isBlank(projectId)) {
                continue;
            }
            num++;
            try {
                syncBaseLineForCRM(projectId);
            } catch (Exception e) {

                log.error("项目推送异常：{}", e.getMessage());
                log.info("项目推送异常：{}", projectId);

            }
        }
        log.info("推送成功总计数据数量：" + num);

    }


    // 获取项目自定义字段ID, 格式 { 名称: id } fixme 提取
    Map<String, String> getProjectCFID(String projectId, List<String> names) {
        List<Map> customField = tbClient.queryProjectCustomField(projectId, null);
        List tList = customField.stream().filter(item -> names.contains(item.get("name"))).collect(Collectors.toList());
        return (Map<String, String>) tList.stream().reduce(UtilMap.empty(), (acc, cur) -> {
            ((Map) acc).put(((Map) cur).get("name"), ((Map) cur).get("id"));
            return acc;
        });
    }

    /**
     * 修改任务自定义字段内容
     *
     * @param projectId 1. 若为空, 触发全量修改; 2. 仅修改非未完成任务
     */
    @Override
    public void batchUpdate(String fieldName, String preName, String modifyName, String projectId) {

        if (StringUtils.isBlank(projectId)) {
            // prd 同步修改: 项目状态为执行 & TB项目号不为空
            YDParam ydParam = YDParam.builder()
                    .formUuid(_matchFormUuid("PROJECT"))
                    .searchFieldJson(JSON.toJSONString(UtilMap.map("textField_llouhiyf", "执行")))
                    .build();
            List<Map> proList = ydService.queryFormData_all(ydParam);
            for (Map pro : proList) {
                String _projectId = UtilMap.getString(pro, "textField_lqxtykce");
                if (StringUtils.isBlank(_projectId)) {
                    continue;
                }
                _batchUpdate(fieldName, preName, modifyName, _projectId);
            }
        } else {
            _batchUpdate(fieldName, preName, modifyName, projectId);
        }
    }

    /// 修改任务自定义字段
    void _batchUpdate(String fieldName, String preName, String modifyName, String projectId) {
        // prd 非未完成任务字段ID
        List<String> workFlowStatusList = _getWorkFlowStatusList(projectId, AWServer.WORKFLOW_INITIAL);

        Map<String, String> proData = getProjectCFID(projectId, Arrays.asList(fieldName));
        String tql = "cf:" + proData.get(fieldName) + " = " + preName + " AND tfsId IN (" + String.join(", ", workFlowStatusList) + ")";

        List<Map> taskList = tbClient.queryProjectTaskList(projectId, UtilMap.put(UtilMap.empty(), "q", tql), null);
        for (Map task : taskList) {
            Map body = TBConf.assembleCustomFieldName(fieldName, modifyName);
            tbClient.updateTaskCustomField(UtilMap.getString(task, "id"), tbConf.getOperatorId(), body);
        }
    }

    /**
     * 同步预检项 [全量同步, 忽略任务号为空记录]
     */
    @Synchronized
    @Override
    public void syncCheckList(YDParam srcParam, Map<String, ?> compIds, String taskCompId, String codeCompId, String checkType, String associationCompId) {

        String curFormUuid = _matchFormUuid("CHECK");
        // 数据来源表
        List<Map> srcList = ydService.queryFormData_all(srcParam);
        // 当前记录表
        List<Map> curList = ydService.queryFormData_all(YDParam.builder()
                .formUuid(curFormUuid)
                .searchFieldJson(JSON.toJSONString(UtilMap.map("selectField_lrnd3h0s", checkType)))
                .build());
        log.info("全量同步 ##, srcList = {}, curList = {}", srcList.size(), curList.size());

        for (Map item : srcList) {
            if (StringUtils.isBlank(UtilMap.getString(item, taskCompId))) {
                continue;
            }
            Map formData = UtilMap.map(associationCompId, YDConf.associationForm(srcParam.getAppType(), srcParam.getFormUuid(), UtilMap.getString(item, "formInstanceId"), UtilMap.getString(item, codeCompId), "", false));
            formData.put("radioField_lrnddfq6", "启用");
            formData.put("selectField_lrnd3h0s", checkType);
            formData.put("textField_lrrnqz82", UtilMap.getString(item, "formInstanceId"));
            for (String compId : compIds.keySet()) {
                formData.put(compId, item.get(compIds.get(compId)));
            }
            YDConf.FORM_OPERATION operate = YDConf.FORM_OPERATION.create;
            YDParam ydParam = YDParam.builder()
                    .formUuid(curFormUuid)
                    .formDataJson(JSON.toJSONString(formData))
                    .updateFormDataJson(JSON.toJSONString(formData))
                    .useLatestVersion(true)
                    .build();
            // 避免循坏内查询, 也避免了模糊匹配
            Optional optional = curList.stream().filter(cur -> cur.get("textField_lrnd3h0r").equals(item.get(codeCompId))).findAny();
            if (optional.isPresent()) {
                ydParam.setFormInstanceId(UtilMap.getString((Map) optional.get(), "formInstanceId"));
                operate = YDConf.FORM_OPERATION.update;
                // prd 0402 兼容删除场景, 标记状态为停用, 不做实际删除
                ((Map) optional.get()).put("status", true);
            }
            ydClient.operateData(ydParam, operate);
        }

        List<Map> dataList = curList.stream().filter(item -> "启用".equals(item.get("radioField_lrnddfq6")) && !item.containsKey("status")).collect(Collectors.toList());
        log.info("兼容删除场景, 标记状态为停用, {}", dataList.size());
        dataList.forEach(item -> {
            ydClient.operateData(YDParam.builder()
                    .formInstanceId(UtilMap.getString(item, "formInstanceId"))
                    .updateFormDataJson(JSON.toJSONString(UtilMap.map("radioField_lrnddfq6", "停用")))
                    .build(), YDConf.FORM_OPERATION.update);
        });
    }

    /**
     * 同步预检项 [通用]
     */
    @Synchronized
    @Override
    public void syncCheckList(int type) {

        log.info("## 同步预检项, {}", type);
        switch (type) {
            case 0:
                // 经验库
                YDParam ydParam1 = YDParam.builder()
                        .appType("APP_D4NPOH9J3KCG5VE1RVN6")
                        .systemToken("6J8668A1B59FA7SAFTAF89PTKKGP2HY3FP3OLV1")
                        .formUuid("FORM-D47CCA076DA54F969DF7442D81E4B5A8PC6Q")
                        .build();
                // 任务号, 描述, ID（标题）, 任务名称, 适用项目类型
                Map<String, ?> compIds1 = UtilMap.map("selectField_lrncf4hk, textField_lrrnqz7w, textField_lrnd3h0r, textField_ltzl9mpc, multiSelectField_ltwjre9s", "selectField_ls9yctsd, textareaField_lo5eakcv, serialNumberField_lo5dmyfb, textField_ls9xvxvk, multiSelectField_ltwjre9s");
                syncCheckList(ydParam1, compIds1, "selectField_ls9yctsd", "serialNumberField_lo5dmyfb", "经验库", "associationFormField_lrrn5csf");
                break;

            case 1:
                // IC技术检查表
                YDParam ydParam2 = YDParam.builder()
                        .appType("APP_NOSSCZQ8FFZKHDQOIIDA")
                        .systemToken("UM6660D1BFTFLFAP7NPJDBJEEAB737FAR1SOLD4")
                        .formUuid("FORM-B40420AFCDB84AD69FE78D82D6D5CFD2C2D2")
                        .build();
                // 任务号, 描述, ID（标题）, 任务名称, 适用项目类型
                Map<String, ?> compIds2 = UtilMap.map("selectField_lrncf4hk, textField_lrrnqz7w, textField_lrnd3h0r, textField_ltzl9mpc, multiSelectField_ltwjre9s, textField_8n5ib3y, textField_rfwx8lk", "selectField_bclg9y5, textField_k1e08ji, textField_96ikaoh, textField_lsa0d856, multiSelectField_ltwjre9s, textField_8n5ib3y, textField_rfwx8lk");
                syncCheckList(ydParam2, compIds2, "selectField_bclg9y5", "textField_96ikaoh", "IC技术检查表", "associationFormField_lrrn5csg");
                break;

            case 2:
                // TR评审要素表
                YDParam ydParam3 = YDParam.builder()
                        .formUuid("FORM-5436DC75BC8347D498DFF2617925BA70O066")
                        .build();
                // 任务号, 描述, ID（标题）, 验收标准, 等级, 任务名称, 适用项目类型
                Map<String, ?> compIds3 = UtilMap.map("selectField_lrncf4hk, textField_lrrnqz7w, textField_lrnd3h0r, textField_cd4f65l, textField_267kho2, textField_ltzl9mpc, multiSelectField_ltwjre9s, textField_rfwx8lk", "selectField_4gwjfa5, textField_lryetn3g, textField_9x1yps6, textField_cd4f65l, textField_267kho2, textField_ls9xvxvk, multiSelectField_ltwjre9s, textField_lryi6ba6");
                syncCheckList(ydParam3, compIds3, "selectField_4gwjfa5", "textField_9x1yps6", "TR评审要素表", "associationFormField_lrrn5csa");
                break;
        }
        log.info("同步预检项 ##, {}", type);
    }

    @Autowired
    private DDClient ddClient;

    @Autowired
    private DDClient_Contacts ddClient_contacts;

    @Autowired
    private DDClient_Storage ddClient_storage;

    /// fixme: 文件/文件夹名称若重复会自动添加(1)\(2)\..., 目前通过自建文件夹/文件数据表控制 [fileType - 文件/文件夹]
    private Map _upsertFileAbsolutePath(String fileType, String fileName, String pCode, String pId, String workspaceId, String rootNodeId, String unionId) {

        // 避免模糊匹配, 知识库ID + 文件/文件夹名称全路径
        List<Map> conditions = new ArrayList<>();
        Map work = new HashMap();
        work.put("key", "textField_lx2yekqj");
        work.put("value", workspaceId);
        work.put("type", "TEXT");
        work.put("operator", "eq");
        work.put("componentName", "TextField");
        conditions.add(work);
        Map file = new HashMap();
        file.put("key", "textField_lx36gdpp");
        file.put("value", fileName);
        file.put("type", "TEXT");
        file.put("operator", "eq");
        file.put("componentName", "TextField");
        conditions.add(file);

        List<Map> dataList = (List<Map>) ydClient.queryData(YDParam.builder()
                .formUuid(_matchFormUuid("DENTRY"))
                .searchCondition(JSON.toJSONString(conditions))
                .build(), YDConf.FORM_QUERY.retrieve_list).getData();
        String formInstId = "";
        String dentryUuid = "";
        int version = 0;
        if (dataList.isEmpty()) {
            Map formData = UtilMap.map("radioField_lx36gdpm, textField_lx36gdpp, textField_lrj7vnxb, textField_lqxtykce", fileType, fileName, pCode, pId);
            formData.put("textField_lx2yekqj", workspaceId);
            if ("文件夹".equals(fileType)) {
                if (fileName.contains("/")) {
                    fileName = String.valueOf(UtilList.getLast(fileName.split("/")));
                }
                DDR_New ddr_new = ddClient_storage.createFolder(ddClient.getAccessToken(), unionId, workspaceId, fileName, "FOLDER", rootNodeId, null);
                rootNodeId = ddr_new.getNodeId(); // ppExt: 文件夹创后, 更新 rootNodeId, 用于创建下级目录
                formData.put("textField_lx36gdpn", ddr_new.getDentryUuid());
                formData.put("textField_lx36gdpo", ddr_new.getUrl());
                formData.put("textField_lx342bvd", rootNodeId);
                dentryUuid = ddr_new.getDentryUuid(); // 用于权限节点打断
            } else {
                formData.put("numberField_lx36gdpz", version);
            }
            formInstId = (String) ydClient.operateData(YDParam.builder()
                    .formUuid(_matchFormUuid("DENTRY"))
                    .formDataJson(JSON.toJSONString(formData))
                    .build(), YDConf.FORM_OPERATION.create);
        } else {
            formInstId = UtilMap.getString(dataList.get(0), "formInstanceId");
            rootNodeId = UtilMap.getString(UtilMap.getMap(dataList.get(0), "formData"), "textField_lx342bvd");
            version = UtilMap.getInt(UtilMap.getMap(dataList.get(0), "formData"), "numberField_lx36gdpz");
        }
        // 返回 formInstId 用于记录文件版本号, 以及复制后文件访问链接
        return UtilMap.map("rootNodeId, formInstId, version, dentryUuid", rootNodeId, formInstId, version, dentryUuid);
    }

    /**
     * 知识库复制操作 sop [ fixme: 文件复制后, 变更记录不会保存 ]
     * -
     * 1. TB 任务上未返回真实 workspaceId
     * - 现状: 返回的 WorkspaceId 其实是 spaceId, instanceId 其实是 dentryId.
     * - 临时: 知识库文件/文件夹复制接口需要传递来源 workplaceId, 但测试下来传递目标 workplaceId 也可以, 包括从我的文档内拷贝均验证通过
     * - 解法: 官方已做了兼容
     * -
     * 2. TB 任务上返回链接格式不一致
     * - 原因: 知识库复制需要同时有文件与\复制目标文件夹下编辑权限. 目前外部使用不能提供公共操作账号, 只能通过 dentryUuid 给来源文件添加编辑权限, 再进行复制操作
     * - 现状: 从最近浏览选择的文件, 地址会被转换, 点击以后会重定向到知识库真实地址, 地址带有 node 与 dentryUuid
     * - 官方: 文档选择器 有最近浏览列表 以及 选知识库后的目录树，背后都是对接的钉钉文档开放API，不过前者是V1版本的，后者是V2版本的，文档他们好像又出了V3版本，总之不同版本接口返回的链接可能是不一样的
     * - 临时: 目前控制用户不能选择最近浏览
     * - 建议: 权限控制需要通过 dentryUuid, TB是否可反回, 或可提供通过 dentry 查询 dentryUuid接口; 或者使用现有链接反查接口, 是否确保链接格式内包含 dentryUuid [URL下node后字符串]
     * - 解法: 提供通过 spaceId + dentryId 查询详情接口, 返回有创建人\dentryUuid信息 [ fixme 特别说明: 知识库 creatorId 就是所有者, 转交后 creatorId 会自动同步更新 ]
     * -
     * 3. 通过接口添加知识库权限操作人需要有权限
     * 说明: 详见2 [fixme: 公共账号拥有所有项目知识库的编辑以上权限]
     * 解法: 通过提供通过 spaceId + dentryId 查询创建人, 再由创建人为公共账号添加编辑权限, 再通过公共账号复制 [ppExt: 知识库可为其他成员添加不高于自身的权限]
     * -
     * 4. 需要通过模板自动创建知识库, TB 134版本已支持
     * 1. 调整: fixme 知识库列表接口, 查询受到 operatorId 权限控制, 仅会返回操作者有权限知识库
     * 2. 方案: 依赖同3, 公共账号 TBManager 默认是项目拥有者, 会默认同步到知识库成员, 查询权限后通过知识库创建者添加编辑, 用于复制文件 [ppExt: 直接添加知识库根节点 rootNodeId 编辑权限]
     * -
     * -5. 高密知识库访问限制: 同一个知识库内可以复制, 产品答复通过接口形态也与实际操作一致的表现, 均有控制不能往外进行复制. 写入是被允许的
     */
    @Override
    public void approveVersion(String taskId, String pCode) {

        String result = "";
        String formInstanceId = "";
        List<String> staffIds = new ArrayList<>();
        List<Map> subList = null;
        String proType = "";

        // 通用账户unionId
        String unionId = String.valueOf(ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), ddConf.getOperator()).get("unionid"));
        // 知识库 & 项目信息处理
        String workspaceId = "", rootNodeId = "", creatorId = "";
        List<Map> pList = ydService.queryDataList_FormData(_matchFormUuid("PROJECT"), UtilMap.map("textField_lrj7vnxb", pCode));
        pList = pList.stream().filter(item -> pCode.equals(item.get("textField_lrj7vnxb"))).collect(Collectors.toList());
        if (pList.isEmpty()) {
            result = "未匹配到项目主数据";
        } else {
            workspaceId = UtilMap.getString(pList.get(0), "textField_lx2yekqj");
            rootNodeId = UtilMap.getString(pList.get(0), "textField_lx342bvd");
            creatorId = UtilMap.getString(pList.get(0), "textField_lx8ra1bs");
            List<Map> details = UtilMap.getList(pList.get(0), "tableField_lqxtykcf");
            for (Map detail : details) {
                if (ObjectUtil.isNotNull(detail.get("employeeField_lqxtykch_id"))) {
                    staffIds.addAll((List) detail.get("employeeField_lqxtykch_id"));
                }
            }
            subList = details; // 子表数据
            proType = UtilMap.getString(pList.get(0), "textField_ltwcq7s6"); // prd 多模板适配: [任务号 + 项目类型]
            if (StringUtils.isBlank(workspaceId)) {
                // 获取知识库空间Id [ ppExt: 知识库列表接口, 查询受到 operatorId 权限控制, 仅会返回操作者有权限知识库 ] todo tb提供接口返回知识库相关信息
                List<Map> workspaces = ddClient_storage.searchWorkspaces(ddClient.getAccessToken(), unionId, pCode, null);
                if (workspaces.isEmpty()) {
                    result = "项目知识库未找到";
                } else {
                    workspaceId = UtilMap.getString(workspaces.get(0), "workspaceId");
                    Map space = ddClient_storage.getWorkspaceDetail(ddClient.getAccessToken(), unionId, workspaceId, false);
                    rootNodeId = UtilMap.getString(space, "rootNodeId"); // 知识库返回根节点 rootNodeId, 即根目录的 dentryUuid
                    creatorId = UtilMap.getString(space, "creatorId");
                    // 更新知识库信息到项目档案
                    formInstanceId = UtilMap.getString(pList.get(0), "instanceId");
                    ydClient.operateData(YDParam.builder()
                            .formInstanceId(formInstanceId)
                            .updateFormDataJson(JSON.toJSONString(UtilMap.map("textField_lx2yekqj, textField_lx342bvd, textField_lx8ra1bs, textField_lx8ra1bt", workspaceId, rootNodeId, creatorId, space.get("url"))))
                            .build(), YDConf.FORM_OPERATION.update);
                }
            }
        }
        // 权限 & 文件复制 & 版本管理
        if (StringUtils.isBlank(result)) {
            try {
                // 知识库返回根节点 rootNodeId, 即根目录的 dentryUuid, 可添加整个知识库编辑权限, 通过知识库创建者赋权
                List<Map> members = Arrays.asList(UtilMap.map("type, id", "USER", ddConf.getOperator()));
                creatorId = String.valueOf(ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), creatorId).get("unionid"));
                ddClient_storage.updateDentryPermissions(ddClient.getAccessToken(), rootNodeId, creatorId, "EDITOR", members, null);
            } catch (McException e) {
                // ppExt: 若是创建人赋值编辑权限, 也会是500异常
                log.error(e.getMessage(), e);
            }
            // 知识库文件夹 & 拷贝处理 [prd 文件目录结构 TR评审节点/资源名称]
            Map taskData = _getTaskFieldMap(taskId, AWServer.TASK_APPROVE_ATTACHMENT, AWServer.TASK_APPROVE_VERSION, AWServer.TASK_STAGE, AWServer.TASK_ROLE, AWServer.TASK_CODE);
            Map rTask = UtilMap.getMap(taskData, "task");
            // fixme: 知识库所有操作和权限都与依赖于权限, 通过 spaceId + dentryId 查询创建人需要传递操作者, 操作者需要有查看以上权限. todo 目前临时读取执行人, tb文档相关信息需要返回创建人信息
            Map<String, String> extra = (Map) tbClient.idMapQuery(UtilMap.getString(rTask, "executorId"), "dingTalk-user", ddConf.getCorpId()).get(0).get("extra");
            String executorIdUnionId = String.valueOf(ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), extra.get("userId")).get("unionid"));
            String workRootNodeId = rootNodeId; // 知识库成员权限同步
            // 文件/文件夹版本控制: fileType, fileName, pCode, pId, workspaceId, rootNodeId, unionId
            String fileName = "文档版本库";
            Map record = _upsertFileAbsolutePath("文件夹", fileName, pCode, UtilMap.getString(rTask, "projectId"), workspaceId, rootNodeId, unionId);
            rootNodeId = UtilMap.getString(record, "rootNodeId"); // 最新文件路径下ID
            /// ppExt 一级目录设置权限打断: 后续知识库成员调整, 非管理者与所有者人员不会传递到当前路径下, 即该文件若要可见需要重新对成员赋权
            log.info("{}, {}", fileName, record);
            if (StringUtils.isNotBlank(UtilMap.getString(record, "dentryUuid"))) {
                _breakPermission(workRootNodeId, UtilMap.getString(record, "dentryUuid"), staffIds, creatorId, formInstanceId);
            }
            String rName = UtilMap.getString(taskData, AWServer.TASK_STAGE);
            if (StringUtils.isBlank(rName)) {
                rName = "其他";
            }
            fileName = fileName + "/" + rName;
            record = _upsertFileAbsolutePath("文件夹", fileName, pCode, UtilMap.getString(rTask, "projectId"), workspaceId, rootNodeId, unionId);
            rootNodeId = UtilMap.getString(record, "rootNodeId"); // 最新文件路径下ID

            String tName = UtilMap.getString(taskData, AWServer.TASK_ROLE);
            if (StringUtils.isBlank(tName)) {
                tName = "其他";
            }
            fileName = fileName + "/" + tName;
            record = _upsertFileAbsolutePath("文件夹", fileName, pCode, UtilMap.getString(rTask, "projectId"), workspaceId, rootNodeId, unionId);
            rootNodeId = UtilMap.getString(record, "rootNodeId"); // 最新文件路径下ID
            List<Map> docs = _getDocs(taskData, AWServer.TASK_APPROVE_ATTACHMENT);
            log.info("docs, {}", docs);
            List<Map> attas = new ArrayList<>();
            for (Map doc : docs) {

                // 文件/文件夹版本控制: fileType, fileName, pCode, pId, workspaceId, rootNodeId, unionId
                String title = UtilMap.getString(doc, "title");
                fileName = fileName + "/" + title;
                Map version = _upsertFileAbsolutePath("文件", fileName, pCode, UtilMap.getString(rTask, "projectId"), workspaceId, rootNodeId, unionId);
                // 通过提供 spaceId + dentryId 查询创建人, 再由创建人为公共账号添加编辑权限, 再通过公共账号复制 [ ppExt: 知识库可为其他成员添加不高于自身的权限 ]
                DDR_New ddr_dentry = ddClient_storage.getSpaceDentryDetail(ddClient.getAccessToken(), executorIdUnionId, UtilMap.getString(doc, "instanceId"), UtilMap.getString(doc, "instanceId"), false);
                try {
                    String creatorId_t = UtilMap.getString(ddr_dentry.getCreator(), "unionId");
                    List<Map> members_t = Arrays.asList(UtilMap.map("type, id", "USER", ddConf.getOperator()));
                    ddClient_storage.updateDentryPermissions(ddClient.getAccessToken(), ddr_dentry.getDentryUuid(), creatorId_t, "EDITOR", members_t, null);
                } catch (McException e) {
                    // ppExt: 若是创建人权限被管理员拿走, 会导致500异常, 需要兼容
                    log.error(e.getMessage(), e);
                }
                // 复制文件/文件夹, 知识库文件添加版本标识
                int order = UtilMap.getInt(version, "version");
                order += 1;
                String docName = title + " v" + order + ".0";
                DDR_New ddr_new;
                Map formData;
                try {
                    ddr_new = ddClient_storage.copyDentries(ddClient.getAccessToken(), unionId, workspaceId, UtilMap.getString(doc, "instanceId"), workspaceId, rootNodeId, docName);
                    String taskCode = UtilMap.getString(taskData, AWServer.TASK_CODE);
                    formData = _updatePermission(taskCode, proType, title, ddr_new.getDentryUuid(), unionId, tName, subList); // 设置文件权限矩阵
                } catch (McException e) {
                    // ppExt: 权限异常可能原因是重复授权, 依然执行复制操作, 再跳过
                    log.error(e.getMessage(), e);
                    continue;
                }
                // 更新最新版本记录, 叠加版本号
                formData = UtilMap.putAll(formData, UtilMap.map("textField_lx36gdpp, numberField_lx36gdpz", fileName, order));
                formData.put("textField_lx342bvd", ddr_new.getDentryId());
                formData.put("textField_lx36gdpn", ddr_new.getDentryUuid());
                formData.put("textField_lx36gdpo", ddr_new.getUrl());
                ydClient.operateData(YDParam.builder()
                        .formInstanceId(UtilMap.getString(version, "formInstId"))
                        .updateFormDataJson(JSON.toJSONString(formData))
                        .useLatestVersion(true)
                        .build(), YDConf.FORM_OPERATION.update);
                // 更新版本字段
                Map meta = UtilMap.map("title, url, instanceId, workspaceId, image", docName, ddr_new.getUrl(), ddr_new.getDentryId(), ddr_new.getSpaceId(), doc.get("image"));
                attas.add(UtilMap.map("title, metaString", docName, JSON.toJSONString(meta)));
            }
            if (attas.isEmpty()) {
                result = "无编辑权限, 复制失败";
            } else {
                // prd 630 多文件传递时, 可能会删除其中交付件, 交付件版本保留全量的记录
                for (Map doc : _getDocs(taskData, AWServer.TASK_APPROVE_VERSION)) {
                    Optional optional = attas.stream().filter(item -> {
                        String curName = UtilMap.getString(doc, "title").split(" v")[0];
                        String preName = UtilMap.getString(item, "title").split(" v")[0];
                        return curName.equals(preName);
                    }).findAny();
                    if (!optional.isPresent()) {
                        attas.add(UtilMap.map("title, metaString", UtilMap.getString(doc, "title"), JSON.toJSONString(doc)));
                    }
                }
                // 多文件更新回写 [ fixme: 更新任务自定义字段值 ]
                tbClient.updateTaskCustomField(taskId, tbConf.getOperatorId(), UtilMap.map("customfieldName, value", AWServer.TASK_APPROVE_VERSION, attas));
            }
        }
        // prd: 变更有单独BPM流程, TBx宜搭只是作为归档管理, 目前交付物审批只有同意和退回操作 [6.18]
        if (StringUtils.isNotBlank(result)) {
            Map body = TBConf.assembleCustomFieldName(AWServer.TASK_APPROVE_VERSION, result);
            tbClient.updateTaskCustomField(taskId, tbConf.getOperatorId(), body);
        }
        log.info("知识库版本管理, {}", result);
    }

    /// 交付件权限设置，包括TBManager账号设置为只读，针对文件. 移除非权限矩阵的项目成员
    private Map _updatePermission(String taskCode, String proType, String title, String dentryUuid, String creatorUnionId, String roleName, List<Map> details) {

        boolean isAll = false;

        // 取值权限矩阵: 获取角色在项目主数据对应成员 [prd: 7.25 通过文件名称模糊匹配, 方案调整]
        //List<Map> tList = ydService.queryDataList_FormData(_matchFormUuid("PERMISSION"), UtilMap.map("selectField_lrncf4hk, radioField_lyvawhud", taskCode, "启用"));
        //tList = tList.stream().filter(item -> taskCode.equals(item.get("selectField_lrncf4hk")) && UtilMap.getList(item, "multiSelectField_ltwjre9s").contains(proType)).collect(Collectors.toList());

        Map search = YDConf.searchCondition_TextFiled("textField_lyv8g8sx", title, "like");
        Map status = YDConf.searchCondition_TextFiled("radioField_lyvawhud", "启用", "eq");
        List<Map> tList = ydService.queryFormData(_matchFormUuid("PERMISSION"), JSON.toJSONString(Arrays.asList(search, status)));
        Map marker = UtilMap.map("textField_lz1jhtch", tList.size()); // 记录权限点, 模糊查询追溯
        List<String> userIds = new ArrayList<>();
        if (tList.size() > 0) {
            List<String> roles = UtilMap.getList(tList.get(0), "multiSelectField_lyv8g8sy");
            if (roles.contains("项目组成员")) {
                isAll = true;
            } else {
                // 兼容: 添加当前任务资源名称角色
                if (!roles.contains(roleName)) {
                    roles.add(roleName);
                }
                for (String roleN : roles) {
                    Optional optional = details.stream().filter(item -> roleN.equals(item.get("selectField_lqxu6bgf"))).findAny();
                    if (optional.isPresent()) {
                        userIds.addAll((List<String>) UtilMap.getList((Map) optional.get(), "employeeField_lqxtykch_id"));
                    }
                }
            }
            marker.put("textField_lz1jhtcg", UtilMap.getString(tList.get(0), "serialNumberField_lqxu8std"));
        } else {
            isAll = true;
        }
        /// 若权限体系包含了 [项目组成员] 或 未匹配到任务权限, 则开放为项目全员可见
        if (isAll) {
            for (Map detail : details) {
                if (ObjectUtil.isNotNull(detail.get("employeeField_lqxtykch_id"))) {
                    userIds.addAll((List) detail.get("employeeField_lqxtykch_id"));
                }
            }
        }
        // 设置权限节点
        ddClient_storage.updateDentryPermissionsInheritances(ddClient.getAccessToken(), dentryUuid, creatorUnionId, "BREAK");
        List<String> staffIds = new ArrayList<>();
        try {  // todo 目前权限绑定群聊会异常, BUG
            List<Map> allMembers = ddClient_storage.queryDentryPermissions(ddClient.getAccessToken(), dentryUuid, creatorUnionId, null);
            for (Map member : allMembers) {
                Map<String, String> team = UtilMap.getMap(member, "member");
                if ("USER".equals(UtilMap.getString(team, "type"))) {
                    String roleId = UtilMap.getString(UtilMap.getMap(member, "role"), "id");
                    String userId = UtilMap.getString(team, "id");
                    // ppExt: 非权限体系内的成员, 以及文件所有者\管理者外, 执行删除
                    if (!Arrays.asList("OWNER", "MANAGER").contains(roleId) && !userIds.contains(userId)) {
                        staffIds.add(userId);
                    }
                }
            }
            // ppExt: 不移除 TBManager 权限, 避免不可见
            if (staffIds.contains(ddConf.getOperator())) {
                staffIds.remove(ddConf.getOperator());
            }
            log.info("文件权限 {}, 删除 = {}, 只读 = {}", dentryUuid, staffIds, userIds);
            _batchUpdatePermission(staffIds, dentryUuid, creatorUnionId, "EDITOR", true);
            _batchUpdatePermission(userIds, dentryUuid, creatorUnionId, "READER", false);
        } catch (McException e) {
            log.error(e.getMessage(), e);
        }
        return marker;
    }


    /// fixme 权限成员列表，最大size30, 通过 partition 快速切割
    private void _batchUpdatePermission(List<String> userIds, String dentryUuid, String creatorUnionId, String roleId, boolean isDelete) {
        List<List<String>> parts = Lists.partition(userIds, DDConf.WORKSPACE_PERMISSION_SIZE);
        for (List<String> ids : parts) {
            List<Map> members = ids.stream().map(userId -> UtilMap.map("type, id", "USER", userId)).collect(Collectors.toList());
            if (isDelete) {
                ddClient_storage.deleteDentryPermissions(ddClient.getAccessToken(), dentryUuid, creatorUnionId, roleId, members, null);
            } else {
                ddClient_storage.updateDentryPermissions(ddClient.getAccessToken(), dentryUuid, creatorUnionId, roleId, members, null);
            }
        }
    }

    /// 更新一级归档目录 dentryUuid 权限打断, 并更新项目成员只读权限, 且更新记录实例ID [ 用于后续项目全量同步时, 刷新权限 ]
    private void _breakPermission(String rootNodeId, String dentryUuid, List<String> staffIds, String creatorUnionId, String formInstanceId) {

        ddClient_storage.updateDentryPermissionsInheritances(ddClient.getAccessToken(), dentryUuid, creatorUnionId, "BREAK");
        try {  // todo 目前权限绑定群聊会异常, BUG
            /// 原存量用户权限处理 & 管理者, 所有者更新异常处理 [ fixme: 需要获取知识库根目录 dentryUuid 成员列表作为权限原始数据 ]
            List<Map> allMembers = ddClient_storage.queryDentryPermissions(ddClient.getAccessToken(), rootNodeId, creatorUnionId, null);
            for (Map member : allMembers) {
                Map<String, String> team = UtilMap.getMap(member, "member");
                // ppExt: 管理员和所有者, 赋值编辑权限, 会报500异常需要兼容 [ 原只读权限不做调整 ]
                if ("USER".equals(UtilMap.getString(team, "type"))) {
                    String roleId = UtilMap.getString(UtilMap.getMap(member, "role"), "id");
                    String userId = UtilMap.getString(team, "id");
                    if (Arrays.asList("OWNER", "MANAGER", "READER").contains(roleId)) {
                        staffIds.remove(userId);
                    } else {
                        /// prd 存量的成员处理办法：第一次更新的时候刷为只读权限，后续知识库成员手动导致的变更不再单独添加。文件权限读取权限矩阵
                        if (StringUtils.isNotBlank(formInstanceId)) {
                            staffIds.add(userId); // ppExt: 原存量的用户, 改为查看权限 [兼容]
                        }
                    }
                }
            }
        } catch (McException e) {
            log.error(e.getMessage(), e);
        }
        // ppExt: 不移除 TBManager 编辑权限, 避免被覆盖
        if (staffIds.contains(ddConf.getOperator())) {
            staffIds.remove(ddConf.getOperator());
        }
        log.info("更新一级归档目录 {}, {}, {}", dentryUuid, staffIds, formInstanceId);
        _batchUpdatePermission(staffIds, dentryUuid, creatorUnionId, "READER", false);
        if (StringUtils.isNotBlank(formInstanceId)) {
            ydClient.operateData(YDParam.builder()
                    .formInstanceId(formInstanceId)
                    .updateFormDataJson(JSON.toJSONString(UtilMap.map("textField_lxmw6z7k", dentryUuid)))
                    .build(), YDConf.FORM_OPERATION.update);
        }
    }

    /// 同步知识库成员权限, 非初始化
    private void _assembleBreakPermission(Map formData) {
        List<String> staffIds = new ArrayList<>();
        String workspaceId = UtilMap.getString(formData, "textField_lx2yekqj");
        if (StringUtils.isNotBlank(workspaceId)) {
            /// ppExt 一级目录设置权限打断: 后续知识库成员调整, 非管理者与所有者人员不会传递到当前路径下, 即该文件若要可见需要重新对成员赋权
            String dentryUuid = UtilMap.getString(formData, "textField_lxmw6z7k");
            String creatorId = UtilMap.getString(formData, "textField_lx8ra1bs");
            List<Map> details = UtilMap.getList(formData, "tableField_lqxtykcf");
            for (Map detail : details) {
                if (ObjectUtil.isNotNull(detail.get("employeeField_lqxtykch_id"))) {
                    staffIds.addAll((List) detail.get("employeeField_lqxtykch_id"));
                }
            }
            String creatorUnionId = String.valueOf(ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), creatorId).get("unionid"));
            _breakPermission(UtilMap.getString(formData, "textField_lx342bvd"), dentryUuid, staffIds, creatorUnionId, null);
        }
    }

    /**
     * 获取主数据中产品列表
     */
    @Override
    public List<Map> getProductList(String projectId, String q) {
        return getProductData(projectId, q, "textField_lhsxurnt", null);
    }

    /**
     * 获取主数据中产品版本
     */
    @Override
    public List<Map> getProductList(String projectId, String q, String taskId) {
        Map taskData = _getTaskFieldMap(taskId, AWServer.TASK_PRODUCT);
//        Map rTask = UtilMap.getMap(taskData, "task");
        String tCode = UtilMap.getString(taskData, AWServer.TASK_PRODUCT);
        McException.assertAccessException(StringUtils.isBlank(tCode), "请先选择产品型号！");
        return getProductData(projectId, q, "textField_lo2ludkv", tCode);
    }

    /**
     * 选择产品\版本后, 回调更新对应原文本字段, 兼容之前字段逻辑
     */
    @Override
    public void custFieldUpdate(JSONObject data) {
        List<Map> list = tbClient.queryTaskDetail(data.getString("taskId"), "", "");
        McException.assertAccessException(list == null || list.size() < 1, "未匹配到任务！");
        Map rTask = list.get(0);
        Map<String, String> proData = getProjectCFID(UtilMap.getString(rTask, "projectId"), Arrays.asList(AWServer.SELECT_TASK_PRODUCT, AWServer.SELECT_TASK_PRODUCT_VERSION));
        if (data.getString("customfieldId").equals(proData.get(AWServer.SELECT_TASK_PRODUCT))) {
            // 更新自定义字段--产品型号
            Map body = null;
            if (data.getJSONArray("customfieldValue").size() < 1) {
                body = TBConf.assembleCustomFieldName(AWServer.TASK_PRODUCT);
            } else {
                body = TBConf.assembleCustomFieldName(AWServer.TASK_PRODUCT, data.getJSONArray("customfieldValue").getJSONObject(0).getString("title"));
            }
            tbClient.updateTaskCustomField(data.getString("taskId"), tbConf.getOperatorId(), body);
        } else if (data.getString("customfieldId").equals(proData.get(AWServer.SELECT_TASK_PRODUCT_VERSION))) {
            // 更新自定义字段--产品版本
            Map body = null;
            if (data.getJSONArray("customfieldValue").size() < 1) {
                body = TBConf.assembleCustomFieldName(AWServer.TASK_PRODUCT_VERSION);
            } else {
                body = TBConf.assembleCustomFieldName(AWServer.TASK_PRODUCT_VERSION, data.getJSONArray("customfieldValue").getJSONObject(0).getString("title"));
            }
            tbClient.updateTaskCustomField(data.getString("taskId"), tbConf.getOperatorId(), body);
        }
    }

    /// 获取产品主数据
    private List<Map> getProductData(String projectId, String q, String qKey, String tCode) {
        // 校验项目号
        YDSearch ytbProjectId = new YDSearch("textField_lqxtykce", projectId, "TB项目编号", YDSearch.Type.TEXT_FIELD, YDSearch.Operator.EQ);
        List<Map> tbProjectList = (List<Map>) ydClient.queryData(YDParam.builder().formUuid("FORM-141E21DF183846028E21727CE43CD1C75CLZ")
                .appType("APP_R5EBUF2FPN3Y8DRF93M4").systemToken("ON566NC1VNIHPANP9TNVHB3TBIWS3E0TUZ5RLF3")
                .searchCondition(JSONObject.toJSONString(Arrays.asList(ytbProjectId)))
                .pageSize(10).build(), YDConf.FORM_QUERY.retrieve_list).getData();

        McException.assertAccessException(tbProjectList == null || tbProjectList.size() < 1, "未匹配到主数据！");
        String pId = UtilMap.getString(UtilMap.getMap(tbProjectList.get(0), "formData"), "textField_lrj7vnxb");
        McException.assertAccessException(StringUtils.isBlank(pId), "未匹配到主数据！");

        YDSearch ydSearch = new YDSearch("textField_lha7mqbs", "G", "物料子类", YDSearch.Type.TEXT_FIELD, YDSearch.Operator.EQ);
        YDSearch ydSearch2 = new YDSearch("textField_lha7mqbn", pId, "项目号", YDSearch.Type.TEXT_FIELD, YDSearch.Operator.EQ);
        YDSearch ydSearch3 = new YDSearch(qKey, q, "产品料号", YDSearch.Type.TEXT_FIELD, YDSearch.Operator.LIKE);
        List qList = Arrays.asList(ydSearch, ydSearch2, ydSearch3);
        if (tCode != null) {
            YDSearch ydSearch4 = new YDSearch("textField_lhsxurnt", tCode, "产品料号", YDSearch.Type.TEXT_FIELD, YDSearch.Operator.EQ);
            qList = Arrays.asList(ydSearch, ydSearch2, ydSearch3, ydSearch4);
        }
        List<Map> list = (List<Map>) ydClient.queryData(YDParam.builder().formUuid("FORM-4W8667D17CAAGFXI9VO0J9J7RFNL2QDFL7AHLJ")
                .appType("APP_QBWQITQBSPJNYTUTNPDK").systemToken("8F966HB12J27MQJM6V4IQDYHYTPA2G4GTZGCLN1")
                .searchCondition(JSONObject.toJSONString(qList))
                .pageSize(100).build(), YDConf.FORM_QUERY.retrieve_list).getData();
        List<Map> result = new ArrayList<>();
        Map<String, String> keyMap = new HashMap<>();
        for (Map map : list) {
            String data = UtilMap.getString(UtilMap.getMap(map, "formData"), qKey);
            if (!StringUtils.isBlank(data) && !keyMap.containsKey(data)) {
                result.add(UtilMap.map("title", data));
                keyMap.put(data, data);
            }
        }
        return result;
    }


    /**
     * 检查项导出, 全部检查项左关联已提交数据 [ ppExt 已提交数据作为查询条件, 只能筛选已提交数据, 因此提供全量导出 ]
     */
    @Synchronized
    @Override
    public List<Map> exportCheckList(String pCode, String proType) {
        List<Map> checkedList = _getCheckedList(pCode);
        // 查询全部检查项提交数据
        List<Map> dataList = ydService.queryFormData_all(YDParam.builder()
                .formUuid(_matchFormUuid("CHECK"))
                //.searchFieldJson(JSON.toJSONString(UtilMap.map("radioField_lrnddfq6", Arrays.asList(proType), "启用")))
                .searchFieldJson(JSON.toJSONString(UtilMap.empty()))
                .dynamicOrder(JSON.toJSONString(UtilMap.map("selectField_lrncf4hk", "+")))
                .build());
        List<Map> unionList = new ArrayList<>();
        // prd RTO 除自身任务号关联任务外, 还会通过规则匹配 IC 技术评审表, 但任务都是重复因此无需左关联, 匹配已提交数据即可
        checkedList.stream().filter(item -> UtilMap.getString(item, "selectField_lqxuswzd").contains("RTO-")).forEach(item -> {
            // RTO- 前缀任务编号会通过左关联匹配, 此处只处理非 RTO 任务且已提交数据
            Optional optional = dataList.stream().filter(check -> {
                Map unique = (Map) UtilMap.getList(item, "associationFormField_lrrnem5r").get(0);
                return !UtilMap.getString(check, "selectField_lrncf4hk").contains("RTO-") && check.get("textField_lrnd3h0r").equals(unique.get("title"));
            }).findAny();
            if (optional.isPresent()) {
                Map check = (Map) optional.get();
                check.put("no", unionList.size() + 1);
                item.putAll(check);
                unionList.add(item);
            }
        });
        // 匹配已提交检查项生数据
        for (Map check : dataList) {
            // 任务编号一致, 检查项唯一标识一致
            List<Map> match = checkedList.stream().filter(item ->
            {
                // ppExt 可能中途有换任务号, 通过实例ID匹配 [ 历史数据存在关联表单标题为任务编号, 非唯一标题 ]
                Map unique = (Map) UtilMap.getList(item, "associationFormField_lrrnem5r").get(0);
                return check.get("instanceId").equals(unique.get("instanceId"));
            }).collect(Collectors.toList());
            // 多条重复提交如何处理
            if (match.size() > 0) {
                // match 匹配已提交件数据, match.main 为已提交数据主表内容
                match.forEach(item -> {
                    check.put("no", unionList.size() + 1);
                    check.put("match", item);
                    check.putAll(item); // 全量, 用于导出
                    unionList.add(check);
                });
            } else {
                check.put("no", unionList.size() + 1);
                unionList.add(check);
            }
        }
        log.info("检查项数据: check = {}, checked = {}, union = {}", dataList.size(), checkedList.size(), unionList.size());
        return unionList;
    }

    /// 查询项目已提交全部检查项数据
    List<Map> _getCheckedList(String pCode) {
        List<Map> dataList = ydService.queryFormData_all(YDParam.builder()
                .formUuid(_matchFormUuid("RECORD"))
                .searchFieldJson(JSON.toJSONString(UtilMap.map("selectField_lqxuswze", pCode)))
                .build());
        dataList = dataList.stream().filter(item -> pCode.equals(item.get("selectField_lqxuswze"))).collect(Collectors.toList());

        List<Map> checkedList = new ArrayList<>();
        for (Map record : dataList) {
            // 检查项清单[明细默认只返回前50]
            List<Map> details = UtilMap.getList(record, "tableField_lqxxgj4s");
            if (details.size() == YDConf.PAGE_SIZE_DETAILS) {
                details = ydService.queryDetails(YDParam.builder()
                        .formUuid(_matchFormUuid("RECORD"))
                        .formInstanceId(UtilMap.getString(record, "instanceId"))
                        .tableFieldId("tableField_lqxxgj4s")
                        .build());
            }
            // 过滤空数据, 无检查项目关联数据
            details = details.stream().filter(item -> ObjectUtil.isNotNull(item) && StringUtils.isNotBlank(UtilMap.getString(item, "associationFormField_lrrnem5r_id")))
                    .map(item -> {
                        // 合并主表笔数据
                        item.put("main", record);
                        item.putAll(record); // 全量, 用于导出
                        item.put("associationFormField_lrrnem5r", JSON.parse(String.valueOf(JSON.parse(UtilMap.getString(item, "associationFormField_lrrnem5r_id")))));
                        return item;
                    }).collect(Collectors.toList());
            log.info("检查项清单, {}", details.size());
            checkedList.addAll(details);
        }
        return checkedList;
    }

    /**
     * 任务NA校验, 一二级不允许NA
     */
    @Override
    public void taskLevelValidate(Map data) {

        String taskId = UtilMap.getString(data, "taskId");
        String projectId = UtilMap.getString(data, "projectId");

        List<String> workIds = _getWorkFlowStatusList(projectId, Arrays.asList("N/A", "NA"));
        if (workIds.contains(UtilMap.getString(data, "tfsId"))) {

            Map taskData = tbClient.queryTaskDetail(taskId, "", "").get(0);
            String parentTaskId = UtilMap.getString(taskData, "parentTaskId");
            if (StringUtils.isNotBlank(parentTaskId)) {
                Map parentTaskData = tbClient.queryTaskDetail(parentTaskId, "", "").get(0);
                parentTaskId = UtilMap.getString(parentTaskData, "parentTaskId");
                if (StringUtils.isBlank(parentTaskId)) {
                    resetWorkFlow(projectId, taskId);
                }
            } else {
                resetWorkFlow(projectId, taskId);
            }
        }
    }

    /// 重置任务状态
    void resetWorkFlow(String projectId, String taskId) {
        List<String> workIds = _getWorkFlowStatusList(projectId, AWServer.WORKFLOW_INITIAL);
        for (String workFlowId : workIds) {
            boolean isSuccess = false;
            try {
                tbClient.updateTaskFlowStatus(taskId, tbConf.getOperatorId(), workFlowId, "一二级任务不允许N/A");
                isSuccess = true;
            } catch (McException e) {

            }
            if (isSuccess) {
                break;
            }
        }
    }

    /************************************* ------------------- *************************************/

    @Override
    public void test() {


        _convertUserId(tbConf.getOperatorId(), true);
        _convertUserId(ddConf.getOperator(), false);

//        exportCheckList("A2453", "产品开发项目");

//        approveVersion("659a681d44ade3345fdc0d39", "99999");
        //
        //        String pCode = "99999";
        //        String pCode = "A240407DryRun";
        //
        //        List<Map> pList = ydService.queryDataList_FormData(_matchFormUuid("PROJECT"), UtilMap.map("textField_lrj7vnxb", pCode));
        //        pList = pList.stream().filter(item -> pCode.equals(item.get("textField_lrj7vnxb"))).collect(Collectors.toList());
        //        _assembleBreakPermission(pList.get(0));

        //        String creatorUnionId = String.valueOf(ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), "095358016629044412").get("unionid"));
        //        List<Map> allMembers = ddClient_storage.queryDentryPermissions(ddClient.getAccessToken(), "14lgGw3P8vMDNPvMC7aw11LzJ5daZ90D", creatorUnionId, null);
        //        log.info("xxxx, {}", allMembers);
        //
        //        String taskId = "65addc57055591ee7d17d807";
        //        Map taskData = _getTaskFieldMap(taskId, AWServer.TASK_APPROVE_LINK, AWServer.TASK_APPROVE_VERSION, AWServer.TASK_STAGE, AWServer.TASK_ROLE);
        //        List<Map> checkList = _getCustomFieldList(taskData, AWServer.TASK_APPROVE_LINK);

        //        checkList = new ArrayList<>();
        //        String url = "https://kabom7.aliwork.com/APP_H7WUJTKB448F9IBDC6C4/processDetail?formUuid=FORM-FBC1A390B4C348089020C763938A6F54RUNY&procInsId=5bd917c4-b23c-4f2f-ba4b-cef533118cdf&isAdmin=true&navConfig.layout=1180";
        //        String url = "https://kabom7.aliwork.com/APP_H7WUJTKB448F9IBDC6C4/submission/FORM-7B63BB056145452F8BC0A2C52492DE00QVBH?taskId=65addc57055591ee7d17d807";
        //        _updateApproveField(taskId, checkList, url, "技术检查项确认", "待提交", AWServer.TASK_APPROVE_LINK);

    }

    @Override
    public void tmp() {
        //        this.tmp_617();
    }

    /// tmp: 6.18 临时修改权限
    private void tmp618() {
        // 知识库返回根节点 rootNodeId, 即根目录的 dentryUuid, 可添加整个知识库编辑权限, 通过知识库创建者赋权
        List<Map> members = Arrays.asList(UtilMap.map("type, id", "USER", ddConf.getOperator()));
        String creatorId = String.valueOf(ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), "Fra2CxAD27BuTqzaEvZ62bDZXBMTPsVg5gIxaxfVS0s").get("unionid"));
        ddClient_storage.updateDentryPermissions(ddClient.getAccessToken(), "Y1OQX0akWmlxny6jF3lrp9om8GlDd3mE", creatorId, "EDITOR", members, null);
    }

    /// tmp: 6.17 处理审批通过数据 [新项目工作量配置异常, 审批通过配置为审核通过]
    private void tmp_617() {

        List<Map> dataList = ydService.queryFormData_all(YDParam.builder()
                .formUuid("FORM-812FD46AF391449A8F206EDB3221B38840UQ")
                .createFromTimeGMT("2024-06-03")
                .build());
        for (Map item : dataList) {
            approved(UtilMap.map("approve, taskId, creatorId, projectId", "审批通过", item.get("textField_lr3dlwsa"), item.get("textField_lr3er4qb"), item.get("textField_lrncs2fu")));
        }
        log.info("处理审批通过数据, {}", dataList.size());
    }

    /// tmp: 5.22 处理检查项重复数据
    private void tmp_522() {
        List<Map> dataList = ydService.queryFormData_all(YDParam.builder()
                .formUuid("FORM-6E2C0D1197264B8AA23EB3FECAE7344B00BN")
                .searchFieldJson(JSON.toJSONString(UtilMap.map("selectField_lqxuswze", "A2328")))
                .build());

        dataList.forEach(item -> {
            List<Map> details = (List<Map>) item.get("tableField_lqxxgj4s");
            String formInstanceId = String.valueOf(item.get("formInstanceId"));
            if (details.size() == 50) {
                YDParam ydParam = YDParam.builder().formUuid("FORM-6E2C0D1197264B8AA23EB3FECAE7344B00BN").formInstanceId(formInstanceId).tableFieldId("tableField_lqxxgj4s").build();
                details = ydService.queryDetails(ydParam);
            }
            if (!details.stream().filter(row -> "技术评审要素表".equals(row.get("selectField_lrnd3h0s"))).findAny().isPresent()) {
                return;
            }
            details = details.stream().filter(row -> !"IC技术检查表".equals(row.get("selectField_lrnd3h0s"))).collect(Collectors.toList());
            details.forEach(record -> {
                //                String tmp = ((List<Map<String, String>>) JSON.parse(String.valueOf(JSON.parse(String.valueOf(record.get("associationFormField_lrrnem5r_id")))))).get(0).get("title");
                //                if (tmp.startsWith("TR")) {
                //                    record.put("selectField_lrnd3h0s", "TR评审要素表");
                //                } else if (tmp.contains("-")) {
                //                    record.put("selectField_lrnd3h0s", "IC技术检查表");
                //                } else {
                //                    record.put("selectField_lrnd3h0s", "经验库");
                //                }
                if ("技术评审要素表".equals(record.get("selectField_lrnd3h0s"))) {
                    record.put("selectField_lrnd3h0s", "IC技术检查表");
                }
                record.put("associationFormField_lrrnem5r", JSON.parse(String.valueOf(record.get("associationFormField_lrrnem5r_id"))));
            });
            log.info("------, {}, {}, {}", details.size(), formInstanceId, details);

            ydClient.operateData(YDParam.builder()
                    .formInstanceId(String.valueOf(item.get("formInstanceId")))
                    .updateFormDataJson(JSON.toJSONString(UtilMap.map("tableField_lqxxgj4s", details)))
                    .build(), YDConf.FORM_OPERATION.update);
        });
    }
}



