package com.yn.bftl.thirdparty.modules.contract.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.qiyuesuo.sdk.v2.bean.*;
import com.qiyuesuo.sdk.v2.http.StreamFile;
import com.qiyuesuo.sdk.v2.json.JSONUtils;
import com.qiyuesuo.sdk.v2.request.*;
import com.qiyuesuo.sdk.v2.response.*;
import com.qiyuesuo.sdk.v2.utils.CryptUtils;
import com.qiyuesuo.sdk.v2.utils.MD5;
import com.yn.bftl.common.common.exception.YnceError;
import com.yn.bftl.common.modules.company.entity.Company;
import com.yn.bftl.common.modules.company.entity.ContractLock;
import com.yn.bftl.common.modules.company.entity.ServiceAccountAuth;
import com.yn.bftl.common.modules.company.enums.CompanyType;
import com.yn.bftl.common.modules.company.enums.InviteStatus;
import com.yn.bftl.common.modules.company.enums.RelationType;
import com.yn.bftl.common.modules.configuration.entity.ThirdPartyPaySet;
import com.yn.bftl.common.modules.meta.entity.MetaFile;
import com.yn.bftl.common.modules.relation.entity.*;
import com.yn.bftl.common.modules.relation.enums.ContractStatus;
import com.yn.bftl.common.modules.relation.enums.FundContent;
import com.yn.bftl.common.modules.subaccount.entity.ElectronicBill;
import com.yn.bftl.common.modules.subaccount.entity.ElectronicBillItem;
import com.yn.bftl.common.modules.subaccount.entity.ElectronicBillItemDaily;
import com.yn.bftl.thirdparty.common.exception.YnceErrorException;
import com.yn.bftl.thirdparty.common.exception.YnceNoRollErrorException;
import com.yn.bftl.thirdparty.common.repository.*;
import com.yn.bftl.thirdparty.common.util.NumberToCN;
import com.yn.bftl.thirdparty.common.util.SpringContextHolder;
import com.yn.bftl.thirdparty.modules.business.service.MetaFileService;
import com.yn.bftl.thirdparty.modules.contract.dto.*;
import com.yn.bftl.thirdparty.modules.contract.enums.ActionType;
import com.yn.bftl.thirdparty.modules.contract.enums.AuthType;
import com.yn.bftl.thirdparty.modules.contract.enums.ContactType;
import com.yn.bftl.thirdparty.modules.contract.repository.ServiceAccountAuthRepository;
import com.yn.bftl.thirdparty.modules.contract.service.ContractLockService;
import com.yn.bftl.thirdparty.modules.contract.abstracts.AbstractContractLockService;
import com.yn.bftl.thirdparty.modules.contract.util.ContractLockUtil;
import com.yn.bftl.thirdparty.modules.contract.vo.CallVo;
import com.yn.bftl.thirdparty.modules.contract.vo.LinkVo;
import com.yn.bftl.thirdparty.modules.contract.vo.LoadInfo;
import com.yn.bftl.thirdparty.modules.contract.vo.StatusVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * $ContractLockServiceImpl 契约锁实现类
 *
 * @author chendehuihuo
 * @date 2022/7/20 11:32
 */
@Slf4j
@Service
public class ImplContractLockService extends AbstractContractLockService implements ContractLockService {

    @Resource
    private CompanyRepository companyRepository;
    @Resource
    private ContractLockRepository contractLockRepository;
    @Resource
    private CrmContractItemRepository crmContractItemRepository;
    @Resource
    private ElectronicReceiptItemRepository electronicReceiptItemRepository;
    @Resource
    private MetaFileService metaFileService;
    @Resource
    private SMArchivesRepository smArchivesRepository;
    @Resource
    private ElectronicBillItemRepository electronicBillItemRepository;
    @Resource
    private ServiceAccountAuthRepository serviceAccountAuthRepository;

    @Value("${file.basePath}")
    private String basePath;

    @Override
    public LinkVo getCompanyAuthLink() {
        if (Boolean.TRUE.equals(getUser().getCompany().getIsContractLockAuth())) {
            throw new YnceErrorException(YnceError.YNCE_216002, "您已认证成功，无需再次认证！");
        }

        Company company = getUser().getCompany();
        ContractLock contractLock = contractLockRepository.findFirstByCompanyId(company.getId());
        CompanyAuthH5PageRequest request;
        // 处于认证中的企业，返回上一次进行到的地方
        if (contractLock != null && contractLock.getInAuthentication() != null && contractLock.getInAuthentication()) {
            request = new CompanyAuthH5PageRequest(contractLock.getName(),
                    new User(contractLock.getPersonalName(), contractLock.getPersonPhone(), ContactType.MOBILE.getValue()));
            request.setRegisterNo(contractLock.getRegisterNo());
            request.setLegalPerson(contractLock.getLegalPerson());
        } else {
            request = new CompanyAuthH5PageRequest(company.getName(), new User(company.getContactName(), company.getContactPhone(), ContactType.MOBILE.getValue()));
            request.setRegisterNo(company.getTaxpayerIdentificationNumber());
            request.setLegalPerson(company.getLegalPerson());
        }
        request.setCallbackUrl(ContractLockUtil.getThirdPartyUrl() + "/contract/call/back/company");
        // request.setCallbackUrl("http://vjetmk.natappfree.cc/contract/call/back/company");

        // 用户可修改字段
        request.setModifyFields("corpName,registerNo,legalPerson");
        CompanyAuthPageResult result = ContractLockUtil.getInstance().service(request, CompanyAuthPageResult.class);
        if (contractLock == null) {
            contractLock = new ContractLock();
            contractLock.setCompany(company);
            contractLock.setAuthType(CompanyType.COMPANY);
        }
        contractLock.setAuthId(result.getRequestId());
        contractLockRepository.save(contractLock);
        return LinkVo.builder().link(result.getAuthUrl()).build();
    }

    @Transactional(rollbackFor = Exception.class, noRollbackFor = YnceNoRollErrorException.class)
    @Override
    public LinkVo getCompanyAuth2Link(CompanyAuthWithlicenseDTO dto) {
        // 如果是已认证且新的营业执照信用代码一致，而法人/企业名称不一致 则返回修改链接，否则返回新的认证链接
        Company company = getUser().getCompany();
        CompanyAuthPageResult result;
        // 是否是修改认证信息
        // 1.认证状态为true且营业执照编码新旧相同
        // 2.认证状态为false(修改中途退出或系统异常，本系统状态已修改但契约锁那边还未发起修改请求)且营业执照编码新旧相同
        ServiceAccountAuth serviceAccountAuth = company.getServiceAccountAuth();
        if (serviceAccountAuth != null && dto.getRegisterNo().equals(serviceAccountAuth.getUnifiedSocialCreditCode())) {
            if (!dto.getName().equals(serviceAccountAuth.getPersonalName()) || !dto.getContact().equals(serviceAccountAuth.getPhoneNumber())) {
                throw new YnceErrorException("修改认证信息，不支持修改用户名和手机号");
            }
            if (dto.getCompanyName().equals(serviceAccountAuth.getName()) && dto.getLegalPerson().equals(serviceAccountAuth.getNameOfLegalPerson())) {
                company.setIsContractLockAuth(true);
                companyRepository.save(company);
                throw new YnceNoRollErrorException("企业信息未变更，无需修改");
            }

            ContractLock contractLock = contractLockRepository.findFirstByCompanyId(company.getId());
            // 当用户在app内嵌页面以外的地方完成修改认证时，本系统只能通过以下接口查询修改信息 【这里只能修改公司名称，修改法人的话只能手动改数据库了】
            if (!dto.getCompanyName().equals(serviceAccountAuth.getName())) {
                CompanyDetailRequest request = new CompanyDetailRequest(dto.getCompanyName());
                com.qiyuesuo.sdk.v2.bean.Company authCompany = ContractLockUtil.getInstance().service(request, com.qiyuesuo.sdk.v2.bean.Company.class);
                // 公司状态：UNREGISTERED（未注册），AUTH_SUCCESS（认证成功）
                if ("AUTH_SUCCESS".equals(authCompany.getStatus())) {
                    serviceAccountAuth.setName(dto.getCompanyName());
                    company.setIsContractLockAuth(true);
                    companyRepository.save(company);
                    contractLock.setInAuthentication(false);
                    contractLock.setName(dto.getCompanyName());
                    contractLockRepository.save(contractLock);
                    throw new YnceNoRollErrorException("企业已认证");
                }
            }

            CompanyChangeInfoRequest request = new CompanyChangeInfoRequest(new User(dto.getName(), dto.getContact(), ContactType.MOBILE.getValue()));
            request.setRegisterNo(dto.getRegisterNo());
            request.setH5(true);
            request.setCallbackUrl(ContractLockUtil.getThirdPartyUrl() + "/contract/call/back/company");
            result = ContractLockUtil.getInstance().service(request, CompanyAuthPageResult.class);

            contractLock.setInAuthentication(true);
            if (dto.getLicense() != null) {
                MetaFile metaFile = metaFileService.fileUpload(dto.getLicense());
                contractLock.setBusinessLicense(metaFile);
            }
            contractLock.setName(dto.getCompanyName());
            contractLock.setRegisterNo(dto.getRegisterNo());
            contractLock.setLegalPerson(dto.getLegalPerson());
            contractLock.setPersonalName(dto.getName());
            contractLock.setPersonPhone(dto.getContact());
            contractLockRepository.save(contractLock);
        } else {
            result = this.companyAuthLicenseRequest(dto);
        }
        if (Boolean.TRUE.equals(company.getIsContractLockAuth())) {
            company.setIsContractLockAuth(false);
            companyRepository.save(company);

            // 失效已生成的合同
            List<CrmContractItem> crmContractItems = crmContractItemRepository.findAllByCompanyRelationPartyAOrCompanyRelationPartyB(company, company);
            if (CollectionUtils.isNotEmpty(crmContractItems)) {
                for (CrmContractItem crmContractItem : crmContractItems) {
                    crmContractItem.setLosed(true);
                }
                crmContractItemRepository.saveAll(crmContractItems);
            }
        }
        return LinkVo.builder().link(result.getAuthUrl()).build();
    }

    private CompanyAuthPageResult companyAuthLicenseRequest(CompanyAuthWithlicenseDTO dto) {
        Company company = getUser().getCompany();
        CompanyAuthLicenseH5PageRequest request = new CompanyAuthLicenseH5PageRequest();
        request.setCompanyName(dto.getCompanyName());
        // 认证提交人信息
        String applicantInfo = JSONUtils.toJson(new User(dto.getName(), dto.getContact(), ContactType.MOBILE.getValue()));
        request.setApplicantInfo(applicantInfo);
        request.setRegisterNo(dto.getRegisterNo());
        request.setLegalPerson(dto.getLegalPerson());
        request.setModifyFields("corpName,registerNo,legalPerson");
        request.setCallbackUrl(ContractLockUtil.getThirdPartyUrl() + "/contract/call/back/company");
        ContractLock contractLock = contractLockRepository.findFirstByCompanyId(company.getId());
        if (contractLock == null) {
            contractLock = new ContractLock();
            contractLock.setCompany(company);
            contractLock.setAuthType(CompanyType.COMPANY);
        }
        try {
            if (dto.getLicense() != null) {
                request.setLicense(new StreamFile(dto.getLicense().getInputStream()));
            } else {
                if (contractLock.getBusinessLicense() == null) {
                    throw new YnceErrorException("请重新上传营业执照");
                }
                FileInputStream file = new FileInputStream(basePath + "/" + contractLock.getBusinessLicense().getFilePath());
                request.setLicense(new StreamFile(file));
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        CompanyAuthPageResult result = ContractLockUtil.getInstance().service(request, CompanyAuthPageResult.class);
        contractLock.setAuthId(result.getRequestId());
        contractLock.setInAuthentication(true);
        if (dto.getLicense() != null) {
            MetaFile metaFile = metaFileService.fileUpload(dto.getLicense());
            contractLock.setBusinessLicense(metaFile);
        }
        contractLock.setName(dto.getCompanyName());
        contractLock.setRegisterNo(dto.getRegisterNo());
        contractLock.setLegalPerson(dto.getLegalPerson());
        contractLock.setPersonalName(dto.getName());
        contractLock.setPersonPhone(dto.getContact());
        contractLockRepository.save(contractLock);
        return result;
    }

    @Override
    public StatusVo getCompanyAuthStatus() {
        Company company = getUser().getCompany();
        ServiceAccountAuth serviceAccountAuth = company.getServiceAccountAuth();
        if (Boolean.TRUE.equals(company.getIsContractLockAuth()) && serviceAccountAuth != null) {
            throw new YnceErrorException(YnceError.YNCE_216002, "您已认证成功，无需再次认证！");
        }
        CompanyAuthResultRequest request = new CompanyAuthResultRequest();
        ContractLock contractLock = contractLockRepository.findFirstByCompanyId(company.getId());
        if (contractLock != null && StringUtils.isNotEmpty(contractLock.getName())) {
            request.setCompanyName(contractLock.getName());
        } else {
            request.setCompanyName(company.getName());
        }
        CompanyAuthResult result = ContractLockUtil.getInstance().service(request, CompanyAuthResult.class);
        SpringContextHolder.getBean(ContractLockService.class).handleCompanyAuthResult(company.getId(), contractLock == null ? null : contractLock.getId(), result);
        // 认证状态，-1（“无认证记录”）、1（“未提交认证申请”）、2（“认证通过”）、3（“认证不通过”）、4（“认证中”）
        Integer status = result.getStatus();
        Map<Integer, String> statusMap = new LinkedHashMap<Integer, String>() {{
            put(-1, "无认证记录");
            put(1, "未提交认证申请");
            put(2, "认证通过");
            put(3, "认证不通过");
            put(4, "认证中");
        }};
        if (status != 2) {
            throw new YnceErrorException(YnceError.YNCE_216002, statusMap.get(status));
        } else {
            return StatusVo.builder().success(Boolean.TRUE).build();
        }
    }

    /**
     * 获取合同
     * 1、创建合同草稿，2、用模板添加合同文档，3、预签署页面（检查、更改合同信息），4、发起合同，5、签署页面
     *
     * @param itemId  合同明细ID
     * @param isLogin 是否登录，不登录默认甲方访问
     * @return
     * @author chendehuihuo
     * @date 2022-07-22 16:12
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public LinkVo show(Long itemId, Boolean isLogin) {
        LoadInfo loadInfo = loadInfo(itemId);
        Company partyA = loadInfo.getPartyA();
        Company company;
        if (isLogin) {
            company = getUser().getCompany();
        } else {
            company = partyA;
        }
        CrmContractItem item = loadInfo.getItem();

        // 判断数据库是否存在合同ID，有的话不能重新生成
        if (StringUtils.isBlank(item.getContractId())) {
            //*** 返回预签署页面url ***//
            Company partyB = item.getCompanyRelation().getPartyB();
            // 如果是第一次去契约锁生成合同，需要校验必填参数，必填参数校验不通过，不允许在契约锁生成合同
            // 必填参数校验
            List<String> errParams = new ArrayList<>();
            switch (item.getCrmContract().getRelationType()) {
                case SUPPLYMARKETING:
                case DEVISE_SERVICE:
                case BRAND_SERVICE:
                    if (StringUtils.isBlank(partyB.getRegisterAddress())) {
                        errParams.add("注册地址");
                    }
                    if (StringUtils.isBlank(partyB.getIdentityCard())) {
                        errParams.add("联系人身份证");
                    }
                    if (StringUtils.isBlank(partyB.getOfficeAddress())) {
                        errParams.add("通讯地址(身份证)");
                    }
                    if (StringUtils.isBlank(partyB.getContactPhone())) {
                        errParams.add("联系人手机");
                    }
                    break;
                case SETTLED_SERVICE:
                case BRAND_AUTHORIZATION:
                    if (StringUtils.isBlank(partyB.getOfficeAddress())) {
                        errParams.add("通讯地址(身份证)");
                    }
                    if (StringUtils.isBlank(partyB.getContactName())) {
                        errParams.add("联系人姓名");
                    }
                    if (StringUtils.isBlank(partyB.getContactPhone())) {
                        errParams.add("联系人手机");
                    }
                    break;
                default:
                    throw new YnceErrorException("合同模板关系类型暂不支持");
            }
            String otherMsg = "";
            if (RelationType.SUPPLYMARKETING.equals(item.getCrmContract().getRelationType())) {
                if (partyB.getCity() == null || StringUtils.isBlank(partyB.getCity().getName())) {
                    errParams.add("销售区域(省市)");
                }

                SMArchives smArchives = smArchivesRepository.findFirstByCompanyRelation(item.getCompanyRelation());
            }
            if (CollectionUtils.isNotEmpty(errParams)) {
                String errMsg = partyB.getName() + "的" + String.join("、", errParams) + "信息为空，请在个人信息中进行完善！";
                throw new YnceErrorException(errMsg);
            }
            if (StringUtils.isNotEmpty(otherMsg)) {
                throw new YnceErrorException(otherMsg);
            }

            // 如果是入驻合同，甲方企业为空
            if (company != null && (partyA == null || !company.getId().equals(partyA.getId()))) {
                throw new YnceErrorException("合同需由甲方先进行审核发起，再开始签署");
            }
            // 创建合同草稿
            createDraft(loadInfo);
            // 用模板添加合同文档
            addByTemplate(loadInfo);
            // 预签署页面
            String appointUrl = appointUrl(item.getContractId());
            return LinkVo.builder().link(appointUrl).build();
        } else if (item.getTimeFrom() == null) {
            String url;
            try {
                //*** 返回预签署页面url ***//
                url = appointUrl(item.getContractId());
                if (company != null && (partyA == null || !company.getId().equals(partyA.getId()))) {
                    throw new YnceErrorException("合同需由甲方先进行审核发起，再开始签署");
                }
            } catch (Exception e) {
                if (e.getMessage().contains("非草稿合同不能调用预签署页面接口")) {
                    //*** 返回签署页面url ***//
                    url = getAppointUrl(company, item);
                } else {
                    throw new YnceErrorException(e.getMessage());
                }
            }
            return LinkVo.builder().link(url).build();
        } else {
            //*** 返回签署页面url ***//
            String pageUrl = getAppointUrl(company, item);
            return LinkVo.builder().link(pageUrl).build();
        }
    }

    @Override
    public Contract getDetails(Long itemId) {
        CrmContractItem item = crmContractItemRepository.findById(itemId).orElseThrow(() -> new YnceErrorException("合同明细不存在！"));
        String contractId = item.getContractId();
        if (StringUtils.isBlank(contractId)) {
            throw new YnceErrorException("契约锁合同ID不存在");
        }
        return this.updateContractStatus(contractId);
    }

    private LoadInfo loadInfo(Long itemId) {
        // 获取合同信息
        CrmContractItem item = crmContractItemRepository.findById(itemId).orElseThrow(() -> new YnceErrorException("合同明细不存在！"));
        Company partyA = item.getCompanyRelation().getPartyA();
        Company partyB = item.getCompanyRelation().getPartyB();
        if (partyA != null) {
            if (!Boolean.TRUE.equals(partyA.getIsContractLockAuth())) {
                throw new YnceErrorException(partyA.getName() + "未认证！");
            }
        }
        if (!Boolean.TRUE.equals(partyB.getIsContractLockAuth())) {
            throw new YnceErrorException(partyB.getName() + "未认证！");
        }
        return LoadInfo.builder().partyA(partyA).partyB(partyB).item(item).build();
    }

    private String getAppointUrl(Company loginCompany, CrmContractItem item) {
        String url;
        if (loginCompany == null && RelationType.SETTLED_SERVICE.equals(item.getCompanyRelation().getType())) {
            ThirdPartyPaySet thirdPartyPaySet = ContractLockUtil.CONFIGURATION;
            if (StringUtils.isEmpty(thirdPartyPaySet.getContractLockPhone())
                    && StringUtils.isEmpty(thirdPartyPaySet.getContractLockEmail())) {
                throw new YnceErrorException("未配置契约锁平台用户手机号/邮箱");
            }
            if (StringUtils.isEmpty(thirdPartyPaySet.getContractLockPhone())) {
                url = pageUrl(item.getContractId(), ContactType.EMAIL.getValue(), thirdPartyPaySet.getContractLockEmail());
            } else {
                url = pageUrl(item.getContractId(), ContactType.MOBILE.getValue(), thirdPartyPaySet.getContractLockPhone());
            }
        } else {
            url = pageUrl(item.getContractId(), ContactType.MOBILE.getValue(), loginCompany.getServiceAccountAuth().getPhoneNumber());
        }
        return url;
    }

    private Contract updateContractStatus(String contractId) {
        // 企业合同
        CrmContractItem item = crmContractItemRepository.findFirstByContractId(contractId);
        Contract result;
        if (item == null) {
            // 经销商账单
            ElectronicBillItem electronicBillItem = electronicBillItemRepository.findFirstByThirdpartyNo(contractId);
            if (electronicBillItem == null) {
                return null;
            }
            ContractDetailRequest request = new ContractDetailRequest(Long.valueOf(contractId));
            result = ContractLockUtil.getInstance().service(request, Contract.class);
            electronicBillItem.setStatus(ContractStatus.valueOf(result.getStatus()));
            electronicBillItemRepository.save(electronicBillItem);
        } else {
            ContractDetailRequest request = new ContractDetailRequest(Long.valueOf(contractId));
            result = ContractLockUtil.getInstance().service(request, Contract.class);
            if (result.getStatus().equals(item.getContractStatus().getValue()) && !ContractStatus.SIGNING.getValue().equals(result.getStatus())) {
                return result;
            }
            // 已完成直接签约成功
            if (ContractStatus.COMPLETE.getValue().equals(result.getStatus())) {
                // 更新合同为成功
                item.setPartyASigned(Boolean.TRUE);
                item.setPartyBSigned(Boolean.TRUE);
                item.setIsSigned(Boolean.TRUE);
                // 签约完成时间（默认甲方是最后签署的，有变更再修改）
                item.setTimeTo(LocalDateTime.parse(result.getSignatories().get(0).getComplateTime(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            } else if (ContractStatus.SIGNING.getValue().equals(result.getStatus()) && (!item.getPartyASigned() || !item.getPartyBSigned())) {
                // 处理甲方或乙方是否签约
                List<Signatory> signatories = result.getSignatories();
                // 目前只针对2个处理
                if (CollectionUtils.isNotEmpty(signatories) && signatories.size() == 2) {
                    // 甲方
                    Signatory signatoryA = signatories.get(0);
                    if ("SIGNED".equals(signatoryA.getStatus())) {
                        item.setPartyASigned(Boolean.TRUE);
                    }
                    // 乙方
                    Signatory signatoryB = signatories.get(1);
                    if ("SIGNED".equals(signatoryB.getStatus())) {
                        item.setPartyBSigned(Boolean.TRUE);
                    }
                }
            }
            item.setContractStatus(ContractStatus.valueOf(result.getStatus()));
            if (StringUtils.isNotEmpty(result.getPublishTime()) && item.getTimeFrom() == null) {
                item.setTimeFrom(LocalDateTime.parse(result.getPublishTime(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            }
            if (StringUtils.isNotBlank(result.getInvalidReason())) {
                item.setInvalidReason(result.getInvalidReason());
            }
            if (StringUtils.isNotBlank(result.getComments())) {
                item.setInvalidReason(result.getComments());
            }
            crmContractItemRepository.save(item);
        }
        return result;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String createDraft(LoadInfo loadInfo) {
        CrmContractItem item = loadInfo.getItem();
        CrmContract crmContract = item.getCrmContract();

        //*****生成契约锁需要的请求参数****//
        Contract draftContract = new Contract();
        // 业务ID 一个合同对应一个bizId，不能重复
        draftContract.setBizId(UUID.randomUUID().toString());
        // 合同主题（合同名称）
        draftContract.setSubject(crmContract.getName());
        // 是否顺序签署；默认为true
        draftContract.setOrdinal(false);
        // 发起方 子公司名称，若需以子公司身份发起合同需要传递该值，默认为对接方公司
        // draftContract.setTenantName(loadInfo.getPartyA().getName());

        // 签署方 甲方
        Signatory signatoryA;
        if (RelationType.SETTLED_SERVICE.equals(item.getCompanyRelation().getType())) {
            ThirdPartyPaySet thirdPartyPaySet = ContractLockUtil.CONFIGURATION;
            if (!org.springframework.util.StringUtils.hasText(thirdPartyPaySet.getContractLockCompany())) {
                throw new YnceErrorException("未配置契约锁平台企业");
            }
            if (!org.springframework.util.StringUtils.hasText(thirdPartyPaySet.getContractLockUsername())) {
                throw new YnceErrorException("未配置契约锁平台用户名称");
            }
            if (!org.springframework.util.StringUtils.hasText(thirdPartyPaySet.getContractLockPhone())
                    && !org.springframework.util.StringUtils.hasText(thirdPartyPaySet.getContractLockEmail())) {
                throw new YnceErrorException("未配置契约锁平台用户手机号/邮箱");
            }

            // 签署方盖章和签字
            signatoryA = new Signatory();
            // 签署方类型：COMPANY（公司），PERSONAL（个人）
            signatoryA.setTenantType(CompanyType.COMPANY.name());
            // 签署方名称
            signatoryA.setTenantName(thirdPartyPaySet.getContractLockCompany());
            // 接收人
            if (org.springframework.util.StringUtils.hasText(thirdPartyPaySet.getContractLockPhone())) {
                signatoryA.setReceiver(
                        new User(thirdPartyPaySet.getContractLockUsername(),
                                thirdPartyPaySet.getContractLockPhone(), ContactType.MOBILE.getValue())
                );
            } else {
                signatoryA.setReceiver(
                        new User(thirdPartyPaySet.getContractLockUsername(),
                                thirdPartyPaySet.getContractLockEmail(), ContactType.EMAIL.getValue())
                );
            }
            setAction(CompanyType.COMPANY, signatoryA);
        } else {
            ContractLock contractLockA = contractLockRepository.findFirstByCompanyId(loadInfo.getPartyA().getId());
            signatoryA = getSignatory(loadInfo.getPartyA(), contractLockA);
            setAction(contractLockA.getAuthType(), signatoryA);
        }
        draftContract.addSignatory(signatoryA);

        // 签署方 乙方
        ContractLock contractLockB = contractLockRepository.findFirstByCompanyId(loadInfo.getPartyB().getId());
        Signatory signatoryB = getSignatory(loadInfo.getPartyB(), contractLockB);
        setAction(contractLockB.getAuthType(), signatoryB);
        draftContract.addSignatory(signatoryB);

        // 不发起合同
        draftContract.setSend(false);
        // 创建合同
        ContractDraftRequest request = new ContractDraftRequest(draftContract);
        Contract result = ContractLockUtil.getInstance().service(request, Contract.class);
        String contractId = result.getId().toString();
        log.info("契约锁创建合同成功，合同ID:{}", contractId);

        List<Signatory> signatories = result.getSignatories();
        if (CollectionUtils.isEmpty(signatories)) {
            throw new YnceErrorException("创建合同草稿错误，返回的签署方为空！");
        }
        List<SignatoryDTO> signatoryDTOS = new ArrayList<>();
        for (Signatory signatory : signatories) {
            List<Action> actions = signatory.getActions();
            List<String> actionIds = new ArrayList<>();
            if (CollectionUtils.isNotEmpty(actions)) {
                for (Action action : actions) {
                    actionIds.add(action.getId().toString());
                }
            }
            signatoryDTOS.add(SignatoryDTO.builder()
                    .signatoryId(signatory.getId().toString())
                    .type(signatory.getTenantType())
                    .actionIds(StringUtils.join(actionIds, ","))
                    .build()
            );
        }
        // 将合同ID 和 SignatoryId、ActionId 保存到数据库
        item.setContractId(contractId);
        item.setAttrs(JSON.toJSONString(signatoryDTOS));
        crmContractItemRepository.save(item);
        return contractId;
    }

    private Signatory getSignatory(Company company, ContractLock contractLock) {
        ServiceAccountAuth serviceAccountAuth = company.getServiceAccountAuth();

        // 签署方盖章和签字
        Signatory signatory = new Signatory();
        // 签署方类型：COMPANY（公司），PERSONAL（个人）
        signatory.setTenantType(contractLock.getAuthType().name());
        // 签署方名称
        if (CompanyType.COMPANY.equals(contractLock.getAuthType())) {
            signatory.setTenantName(serviceAccountAuth.getName());
        } else {
            signatory.setTenantName(serviceAccountAuth.getPersonalName());
        }
        // 接收人
        signatory.setReceiver(
                new User(serviceAccountAuth.getPersonalName(),
                        serviceAccountAuth.getPhoneNumber(), ContactType.MOBILE.getValue())
        );

        return signatory;
    }

    private void setAction(CompanyType authType, Signatory signatory) {
        // 签署动作（签署流程）
        Action action = new Action();
        if (CompanyType.COMPANY.equals(authType)) {
            action.setType(ActionType.COMPANY.getValue());
            action.setSerialNo(0);
            signatory.addAction(action);

            Action action1 = new Action();
            action1.setType(ActionType.OPERATOR.getValue());
            action1.setSerialNo(1);
            signatory.addAction(action1);
        } else {
            action.setType(ActionType.OPERATOR.getValue());
            action.setSerialNo(0);
            signatory.addAction(action);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Long addByTemplate(LoadInfo loadInfo) {
        CrmContractItem item = loadInfo.getItem();
        CrmContract crmContract = item.getCrmContract();

        // 设置合同模板参数
        List<TemplateParam> params = new ArrayList<>();
        try {
            switch (crmContract.getRelationType()) {
                case SUPPLYMARKETING:
                    SpringContextHolder.getBean(ContractLockParams.class).toSupplymarketing(loadInfo, params);
                    break;
                case SETTLED_SERVICE:
                    SpringContextHolder.getBean(ContractLockParams.class).toSettledService(loadInfo, params);
                    break;
                case DEVISE_SERVICE:
                    SpringContextHolder.getBean(ContractLockParams.class).toDeviseService(loadInfo, params);
                    break;
                case BRAND_SERVICE:
                    SpringContextHolder.getBean(ContractLockParams.class).toBrandService(loadInfo, params);
                    break;
                case BRAND_AUTHORIZATION:
                    SpringContextHolder.getBean(ContractLockParams.class).toBrandAuthorization(loadInfo, params);
                    break;
                default:
                    throw new YnceErrorException(YnceError.YNCE_216002, "合同模板关系类型暂不支持");
            }
        } catch (Exception e) {
            log.error("合同模板参数错误 - ", e);
            throw new YnceErrorException("合同模板参数错误：" + e.getMessage());
        }

        DocumentAddByTemplateRequest request = new DocumentAddByTemplateRequest(
                Long.valueOf(item.getContractId()), Long.valueOf(crmContract.getSn()), params, crmContract.getName());
        DocumentAddResult result = ContractLockUtil.getInstance().service(request, DocumentAddResult.class);
        log.info("契约锁添加合同文档成功，文档ID:{}", result.getDocumentId());
        item.setDocumentId(result.getDocumentId() + "");
        crmContractItemRepository.save(item);
        return result.getDocumentId();
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void send(Long itemId, Boolean isLogin) {
        CrmContractItem item = crmContractItemRepository.findById(itemId).orElseThrow(() -> new YnceErrorException("合同明细不存在！"));
        if (StringUtils.isEmpty(item.getContractId())) {
            throw new YnceErrorException("合同还未在契约锁生成");
        }
        Company partyA = item.getCompanyRelation().getPartyA();
        Company company;
        if (isLogin) {
            company = getUser().getCompany();
        } else {
            company = partyA;
        }
        // 如果是入驻合同，甲方企业为空
        if (company != null && (partyA == null || !company.getId().equals(partyA.getId()))) {
            throw new YnceErrorException("只能由甲方发起合同");
        }
        if (StringUtils.isEmpty(item.getDocumentId())) {
            throw new YnceErrorException("合同还未生成，请先进行审核");
        }
        Long documentId = Long.valueOf(item.getDocumentId());
        RelationType relationType = item.getCrmContract().getRelationType();
        List<SignatoryDTO> signatoriesList = JSON.parseArray(item.getAttrs(), SignatoryDTO.class);

        List<Stamper> stampers = new ArrayList<>();
        // 当前这里只有两个数据，甲乙
        // 甲方签约位置关键字
        SignatoryDTO signatoryDTOA = signatoriesList.get(0);
        List<String> keywordsA = new ArrayList<>();
        keywordsA.add("甲方（盖章）：");
        // 乙方签约位置关键字
        SignatoryDTO signatoryDTOB = signatoriesList.get(1);
        List<String> keywordsB = new ArrayList<>();
        keywordsB.add("乙方（盖章）：");
        switch (relationType) {
            case SETTLED_SERVICE:
            case BRAND_AUTHORIZATION:
                keywordsA.add("法定代表人或授权委托人：");
                keywordsA.add("签订日期：");
                keywordsB.add("法定代表人或授权委托人：");
                keywordsB.add("签订日期：");
                break;
            case SUPPLYMARKETING:
            case DEVISE_SERVICE:
            case BRAND_SERVICE:
                keywordsA.add("法定代表人或授权委托人：");
                keywordsA.add("签订日期：");
                keywordsB.add("法定代表人或经营者：");
                keywordsB.add("签订日期：");
                break;
            default:
        }
        // 甲方签约位置
        addStampers(signatoryDTOA, documentId, keywordsA, 1, stampers);
        // 乙方签约位置
        addStampers(signatoryDTOB, documentId, keywordsB, -1, stampers);
        if (RelationType.BRAND_AUTHORIZATION.equals(relationType)) {
            String actionIds = signatoryDTOB.getActionIds();
            String[] actionIdArr = actionIds.split(",");
            // 附件一签署位置
            addAttachmentStampers(documentId, actionIdArr, 1, stampers);
            // 附件二签署位置
            addAttachmentStampers(documentId, actionIdArr, 2, stampers);
        }

        // 发起合同
        ContractSendRequest request = new ContractSendRequest(Long.valueOf(item.getContractId()), stampers);
        request.setStamperRule("MUST_FORBID");
        Object result = ContractLockUtil.getInstance().service(request, Object.class);
        log.info("合同发起成功：{}", result);
        // 签约开始时间
        item.setTimeFrom(LocalDateTime.now());
        crmContractItemRepository.save(item);
    }

    private void addStampers(SignatoryDTO signatoryDTO, Long documentId, List<String> keywords, int index, List<Stamper> stampers) {
        // 添加时间
        Stamper stamperDate = new Stamper();
        if (AuthType.COMPANY.getValue().equals(signatoryDTO.getType())) {
            String actionIds = signatoryDTO.getActionIds();
            String[] actionIdArr = actionIds.split(",");

            Stamper stamper = new Stamper();
            stamper.setType("COMPANY");
            stamper.setActionId(Long.valueOf(actionIdArr[0]));
            stamper.setDocumentId(documentId);
            stamper.setKeyword(keywords.get(0));
            stamper.setOffsetX(-0.05);
            stamper.setOffsetY(-0.05);
            stampers.add(stamper);

            Stamper stamper1 = new Stamper();
            stamper1.setType("PERSONAL");
            stamper1.setActionId(Long.valueOf(actionIdArr[1]));
            stamper1.setDocumentId(documentId);
            stamper1.setKeyword(keywords.get(1));
            stamper1.setKeywordIndex(index);
            stamper1.setOffsetX(-0.03);
            stamper1.setOffsetY(-0.005);
            stampers.add(stamper1);

            if (index == 1) {
                // 加盖骑缝章
                Stamper stamper2 = new Stamper();
                stamper2.setType("ACROSS_PAGE");
                stamper2.setActionId(Long.valueOf(actionIdArr[0]));
                stamper2.setDocumentId(documentId);
                stamper2.setOffsetX(0.5);
                stamper2.setOffsetY(0.5);
                stampers.add(stamper2);
            }

            stamperDate.setActionId(Long.valueOf(actionIdArr[1]));
        } else {
            Stamper stamper = new Stamper();
            stamper.setType("PERSONAL");
            stamper.setSignatoryId(Long.valueOf(signatoryDTO.getSignatoryId()));
            stamper.setDocumentId(documentId);
            stamper.setKeyword("签字：");
            stamper.setKeywordIndex(index);
            stamper.setOffsetX(-0.03);
            stamper.setOffsetY(-0.005);
            stampers.add(stamper);

            stamperDate.setSignatoryId(Long.valueOf(signatoryDTO.getSignatoryId()));
        }

        stamperDate.setType("TIMESTAMP");
        stamperDate.setDocumentId(documentId);
        stamperDate.setKeyword(keywords.get(2));
        stamperDate.setKeywordIndex(index);
        stamperDate.setOffsetX(-0.01);
        stamperDate.setOffsetY(-0.005);
        // 需要在契约锁平台系统显示设置中添加签署日期格式支持，否则不允许使用此格式
        stamperDate.setDatePatterns(Collections.singletonList("Chinese"));
        stampers.add(stamperDate);
    }

    private void addAttachmentStampers(Long documentId, String[] actionIdArr, int index, List<Stamper> stampers) {
        // 保证人（签章）【盖章】
        Stamper sealStamper = new Stamper();
        sealStamper.setType("COMPANY");
        sealStamper.setActionId(Long.valueOf(actionIdArr[0]));
        sealStamper.setDocumentId(documentId);
        sealStamper.setKeyword("保证方（签章）：");
        sealStamper.setKeywordIndex(index);
        sealStamper.setOffsetX(-0.03);
        sealStamper.setOffsetY(-0.02);
        stampers.add(sealStamper);

        // 保证人（签章）【签名】
        Stamper signStamper = new Stamper();
        signStamper.setType("PERSONAL");
        signStamper.setActionId(Long.valueOf(actionIdArr[1]));
        signStamper.setDocumentId(documentId);
        signStamper.setKeyword("保证方（签章）：");
        signStamper.setKeywordIndex(index);
        signStamper.setOffsetX(-0.03);
        signStamper.setOffsetY(-0.01);
        stampers.add(signStamper);

        // 时间
        Stamper dateTamper = new Stamper();
        dateTamper.setType("TIMESTAMP");
        dateTamper.setActionId(Long.valueOf(actionIdArr[1]));
        dateTamper.setDocumentId(documentId);
        dateTamper.setKeyword("保证方（签章）：");
        dateTamper.setKeywordIndex(index);
        dateTamper.setOffsetX(-0.01);
        dateTamper.setOffsetY(-0.025);
        // 需要在契约锁平台系统显示设置中添加签署日期格式支持，否则不允许使用此格式
        dateTamper.setDatePatterns(Collections.singletonList("Chinese"));
        stampers.add(dateTamper);
    }

    @Override
    public String appointUrl(String contractId) {
        ContractAppointPageRequest appointPageRequest = new ContractAppointPageRequest();
        appointPageRequest.setContractId(Long.valueOf(contractId));
        // 跳转页面类型，指定签署位置页面：APPOINT，参数填写页面：FILL_PARAMETERS，基础信息编辑页面：DRAFT，默认为指定签署位置页面
        appointPageRequest.setPageType("FILL_PARAMETERS");
        // 不允许发起，由前端调本系统接口发起
        appointPageRequest.setAllowedSend(false);
        ContractPageResult result = ContractLockUtil.getInstance().service(appointPageRequest, ContractPageResult.class);
        return result.getPageUrl();
    }

    @Override
    public String pageUrl(String contractId, String contactType, String contact) {
        // 更新合同状态
        this.updateContractStatus(contractId);
        // 合同页面
        ContractPageRequest request = new ContractPageRequest(Long.valueOf(contractId),
                new User(contact, contactType), "");
        ContractPageResult result = ContractLockUtil.getInstance().service(request, ContractPageResult.class);
        return result.getPageUrl();
    }

    @Override
    public void download(Long documentId, HttpServletResponse response) {
        DocumentDownloadRequest request = new DocumentDownloadRequest(documentId);
        try {
            response.setContentType("application/force-download");// 设置强制下载不打开
            response.addHeader("Content-Disposition", "attachment;fileName=" + documentId + ".pdf");// 设置文件名
            ContractLockUtil.getInstance().sdkClient().download(request, response.getOutputStream());
        } catch (Exception e) {
            log.error("下载合同文档失败 - ", e);
            throw new YnceErrorException("下载失败，请稍后重试！");
        }
    }

    @Override
    public void downloadZip(Long contractId, HttpServletResponse response) {
        ContractDownloadRequest request = new ContractDownloadRequest(contractId);
        // 下载子项目，子项目可选项：CONTRACT（"合同原文"）、SIGNLOG（"签署日志"）、ATTACHMENT（"附件"）、
        // NOTARY（"存证报告"）、ENDSIGN_ATTACHMENT（“强制结束附件”），各子项以逗号（","）相隔，
        // 如：CONTRACT,SIGNLOG，默认为合同文件与签署日志
        List<String> items = new ArrayList<String>() {{
            add("CONTRACT");
            add("SIGNLOG");
            add("NOTARY");
        }};
        request.setDownloadItems(items);
        try {
            response.setContentType("application/force-download");// 设置强制下载不打开
            response.addHeader("Content-Disposition", "attachment;fileName=" + contractId + ".zip");// 设置文件名
            ContractLockUtil.getInstance().sdkClient().download(request, response.getOutputStream());
        } catch (Exception e) {
            log.error("下载合同与附属文件 - ", e);
            throw new YnceErrorException("下载失败，请稍后重试！");
        }
    }

    @Override
    public String callBack(String signature, String timestamp, HttpServletRequest request) {
        String content = request.getParameter("content");
        log.info("契约锁合同回调 signature: {}, timestamp：{}, content: {}", signature, timestamp, content);
        writeUserLog("契约锁合同回调 signature: " + signature + ", timestamp：" + timestamp + ", content: " + content);

        String secretKey = ContractLockUtil.CONFIGURATION.getContractLockSecretKey();
        // 验证signature是否正确
        String signatureLocal = MD5.toMD5(timestamp + secretKey);
        if (!signatureLocal.equals(signature)) {
            log.warn("契约锁合同回调，signature校验不正确，回调signature：{}， 回调timestamp：{}，自己生成signature：{}", signature, timestamp, signatureLocal);
            writeUserLog("[契约锁][合同回调]signature校验不正确");
            return "fail";
        }

        try {
            // 解密content
            String contentStr = CryptUtils.aesDerypt(content, secretKey);
            JSONObject contentJson = JSON.parseObject(contentStr);
            // 当前回调的合同Id
            String contractId = contentJson.getString("contractId");
            // 当前回调的通知类型
            String callbackType = contentJson.getString("callbackType");
            // 当前回调的合同状态
            String contractStatus = contentJson.getString("contractStatus");
            // 当前直接查询合同详情，通过合同详情验证两方是否完成签字
            this.updateContractStatus(contractId);
        } catch (Exception e) {
            log.warn("契约锁合同回调，content解密失败 - ", e);
            writeUserLog("[契约锁][合同回调]content解密失败：" + e.getMessage());
            return "fail";
        }
        return "success";
    }

    @Override
    public CallVo companyCallback(Integer status, Integer actionEvent, String requestId, String authInfo) {
        log.info("契约锁企业认证回调 status: {}, actionEvent：{}, requestId: {}, authInfo: {}", status, actionEvent, requestId, authInfo);
        writeUserLog("契约锁企业认证回调 status: " + status + ", actionEvent：" + actionEvent + ", requestId: " + requestId + ", authInfo: " + authInfo);
        ContractLock contractLock = contractLockRepository.findFirstByAuthId(requestId);
        log.warn("契约锁企业认证回调 根据 requestId:" + requestId + " 没有查询到数据");
        // 用户可能多次发起了【获取企业认证链接】的接口，造成新的 requestId 替换了旧的 requestId，所以如果查询不到数据，则使用 authInfo 中的企业名称尝试查询
        if (contractLock == null) {
            Map<String, Object> map = JSONObject.parseObject(authInfo, Map.class);
            contractLock = contractLockRepository.findByNameAndRegisterNoAndCompanyIsNotNull(map.get("name").toString(), map.get("registerNo").toString());
            if (contractLock == null) {
                log.error("契约锁企业认证回调 - 未查询到认证记录");
                return CallVo.builder().code(1001).build();
            }
        }
        Company company = contractLock.getCompany();
        ServiceAccountAuth serviceAccountAuth = company.getServiceAccountAuth();
        if (Boolean.TRUE.equals(company.getIsContractLockAuth()) && serviceAccountAuth != null) {
            log.warn("契约锁企业已认证成功！");
            return CallVo.builder().code(0).build();
        }

        CompanyAuthResultRequest request = new CompanyAuthResultRequest();
        request.setCompanyName(contractLock.getName());
        CompanyAuthResult result = ContractLockUtil.getInstance().service(request, CompanyAuthResult.class);
        SpringContextHolder.getBean(ContractLockService.class).handleCompanyAuthResult(company.getId(), contractLock.getId(), result);
        return CallVo.builder().code(0).build();
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void handleCompanyAuthResult(Long companyId, Long contractLockId, CompanyAuthResult result) {
        Integer resultStatus = result.getStatus();
        // 认证状态，-1（“无认证记录”）、1（“未提交认证申请”）、2（“认证通过”）、3（“认证不通过”）、4（“认证中”）
        if (resultStatus == 2 || resultStatus == 4 || resultStatus == 3) {
            Company company = companyRepository.findById(companyId).orElseThrow(() -> new YnceErrorException("企业不存在"));
            ContractLock contractLock;
            if (contractLockId != null) {
                contractLock = contractLockRepository.findById(contractLockId).orElseThrow(() -> new YnceErrorException("契约锁认证提交信息不存在"));
            } else {
                contractLock = new ContractLock();
                contractLock.setAuthType(CompanyType.COMPANY);
                contractLock.setCompany(company);
            }
            contractLock.setName(result.getName());
            contractLock.setRegisterNo(result.getRegisterNo());
            contractLock.setLegalPerson(result.getLegalPerson());
            contractLock.setPersonalName(result.getApplicantName());
            contractLock.setPersonPhone(result.getApplicantPhone());

            // 认证通过
            if (resultStatus == 2) {
                contractLock.setAuthTime(LocalDateTime.now());
                contractLock.setInAuthentication(false);

                ServiceAccountAuth serviceAccountAuth = company.getServiceAccountAuth();
                if (serviceAccountAuth == null) {
                    serviceAccountAuth = new ServiceAccountAuth();
                }
                serviceAccountAuth.setAuthType(com.yn.bftl.common.modules.company.enums.AuthType.COMPANY);
                serviceAccountAuth.setName(result.getName());
                serviceAccountAuth.setUnifiedSocialCreditCode(result.getRegisterNo());
                serviceAccountAuth.setNameOfLegalPerson(result.getLegalPerson());
                serviceAccountAuth.setPersonalName(result.getApplicantName());
                serviceAccountAuth.setPhoneNumber(result.getApplicantPhone());
                MetaFile businessLicense = contractLock.getBusinessLicense();
                if (businessLicense != null) {
                    MetaFile metaFile = new MetaFile();
                    BeanUtils.copyProperties(businessLicense, metaFile, "id", "version", "createdOn");
                    serviceAccountAuth.setBusinessLicense(metaFile);
                }
                serviceAccountAuth.setCompany(company);
                serviceAccountAuth.setInviteStatus(InviteStatus.NOT_INVITED);
                serviceAccountAuthRepository.saveAndFlush(serviceAccountAuth);
                company.setIsContractLockAuth(Boolean.TRUE);
                companyRepository.save(company);
            } else {
                // 认证中、认证不通过
                contractLock.setInAuthentication(true);
            }
            contractLockRepository.save(contractLock);
        }
    }

    @Override
    public String getDetailUrl(Long id) {
        CrmContractItem item = crmContractItemRepository.findById(id).orElseThrow(() -> new YnceErrorException("合同明细不存在！"));
        if (StringUtils.isEmpty(item.getContractId())) {
            throw new YnceErrorException("合同还未在契约锁生成");
        }
        // 更新合同状态
        this.updateContractStatus(item.getContractId());
        ContractViewPageRequest request = new ContractViewPageRequest(Long.valueOf(item.getContractId()));
        ContractPageResult result = ContractLockUtil.getInstance().service(request, ContractPageResult.class);
        return result.getPageUrl();
    }

    @Override
    public String detailUrl(Long contractId) {
        ContractViewPageRequest request = new ContractViewPageRequest(contractId);
        ContractPageResult result = ContractLockUtil.getInstance().service(request, ContractPageResult.class);
        log.info("契约锁合同详情url:{}", result.getPageUrl());
        return result.getPageUrl();
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void generateReceipt() {
        List<ElectronicReceiptItem> items = electronicReceiptItemRepository.findByInvalidIsFalseAndThirdpartyNoIsNull();
        if (CollectionUtils.isEmpty(items)) {
            return;
        }

        Map<Long, List<ElectronicReceiptItem>> companyMap = items.stream()
                .collect(Collectors.groupingBy(item -> item.getsMArchives().getCompanyRelation().getPartyA().getId()));
        Set<Map.Entry<Long, List<ElectronicReceiptItem>>> entries = companyMap.entrySet();
        List<ElectronicReceiptItem> waitingItems = new ArrayList<>();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
        String invoiceDate = formatter.format(LocalDate.now());
        for (Map.Entry<Long, List<ElectronicReceiptItem>> entry : entries) {
            List<ElectronicReceiptItem> values = entry.getValue();
            Company partyA = values.get(0).getsMArchives().getCompanyRelation().getPartyA();
            if (!partyA.getIsContractLockAuth()) {
                log.warn("{}企业契约锁未认证，无法生成收据", partyA.getName());
                continue;
            }
            ServiceAccountAuth serviceAccountAuth = partyA.getServiceAccountAuth();
            for (ElectronicReceiptItem value : values) {
                ElectronicReceipt electronicReceipt = value.getElectronicReceipt();
                Contract draftContract = new Contract();
                // 业务ID 一个合同对应一个bizId，不能重复
                draftContract.setBizId(UUID.randomUUID().toString());
                draftContract.setTenantName(serviceAccountAuth.getName());
                // 是否发起合同
                draftContract.setSend(true);
                // 业务分类
                draftContract.setCategory(new Category(Long.valueOf(electronicReceipt.getClassifyCode())));
                // 签署方
                Signatory signatory = new Signatory();
                signatory.setTenantType(CompanyType.COMPANY.name());
                signatory.setTenantName(serviceAccountAuth.getName());
                signatory.setReceiver(new User(serviceAccountAuth.getPersonalName(),
                        serviceAccountAuth.getPhoneNumber(), ContactType.MOBILE.getValue()));
                Action action = new Action(ActionType.COMPANY.getValue(), 1);
                signatory.addAction(action);
                // 是否可以自动签
                action.setAutoSign(true);
                draftContract.addSignatory(signatory);
                // 模板参数
                List<TemplateParam> templateParams = new ArrayList<TemplateParam>() {{
                    add(new TemplateParam("收款方", serviceAccountAuth.getName()));
                    add(new TemplateParam("开票日期", invoiceDate));
                    add(new TemplateParam("收据编号", value.getCode()));
                    add(new TemplateParam("付款方", value.getsMArchives().getCompanyRelation().getPartyB().getName()));
                    add(new TemplateParam("款项内容", electronicReceipt.getFundContent().getName()));
                    add(new TemplateParam("收款方式", electronicReceipt.getReceiptType().getName()));
                }};
                if (FundContent.CREDIT_CASH.equals(electronicReceipt.getFundContent())) {
                    templateParams.add(new TemplateParam("人民币大写", NumberToCN.numberToCn(BigDecimal.ZERO)));
                    templateParams.add(new TemplateParam("人民币小写", "￥" + BigDecimal.ZERO));
                } else if (FundContent.FINISH_CASH.equals(electronicReceipt.getFundContent())) {
                    templateParams.add(new TemplateParam("人民币大写", NumberToCN.numberToCn(BigDecimal.ZERO)));
                    templateParams.add(new TemplateParam("人民币小写", "￥" + BigDecimal.ZERO));
                }
                draftContract.setTemplateParams(templateParams);

                // 生成合同并自动签署
                ContractDraftRequest request = new ContractDraftRequest(draftContract);
                try {
                    Contract result = ContractLockUtil.getInstance().service(request, Contract.class);
                    value.setThirdpartyNo(result.getId() + "");
                    value.setAttrs(result.getDocuments().get(0).getId() + "");
                    waitingItems.add(value);
                    log.info("生成收据合同：{}", JSONObject.toJSONString(result));
                } catch (Exception e) {
                    log.warn("生成收据合同失败 - ", e);
                }
            }
        }

        if (CollectionUtils.isNotEmpty(waitingItems)) {
            electronicReceiptItemRepository.saveAll(waitingItems);
        }
    }

    @Override
    public boolean invalid(Long contractId, String invalidReason) {
        // “已完成”状态下请求作废合同，同时发起方签署作废合同
        ContractInvalidRequest request = new ContractInvalidRequest(contractId, null, invalidReason, false);
        try {
            ContractLockUtil.getInstance().service(request, Object.class);
        } catch (Exception e) {
            if (e.getMessage().contains("INVALID CONTRACT STATUS")) {
                // 契约锁合同已作废，直接返回成功
                return true;
            } else {
                String[] split = e.getMessage().split("#");
                if (split.length >= 2) {
                    throw new YnceErrorException(split[1]);
                } else {
                    throw new YnceErrorException(e.getMessage());
                }
            }
        }
        return true;
    }

    @Override
    public void anew(Long companyId) {
        ContractLock contrackLock = contractLockRepository.findFirstByCompanyId(companyId);
        if (contrackLock == null) {
            throw new YnceErrorException("数据不存在");
        }
        contrackLock.setCompany(null);
        contractLockRepository.save(contrackLock);
    }

    @Override
    public void update(Long companyId, String name, String code, String legalPerson, Boolean authentication) {
        ContractLock contrackLock = contractLockRepository.findFirstByCompanyId(companyId);
        if (contrackLock == null) {
            throw new YnceErrorException("数据不存在");
        }
        contrackLock.setName(name);
        contrackLock.setRegisterNo(code);
        contrackLock.setLegalPerson(legalPerson);
        contrackLock.setInAuthentication(authentication);
        contractLockRepository.save(contrackLock);
    }

    @Override
    public ContractLock contractLock(Long companyId) {
        return contractLockRepository.findFirstByCompanyId(companyId);
    }

    @Override
    public String statement(Long id) {
        Company company = getUser().getCompany();
        if (!company.getIsContractLockAuth()) {
            throw new YnceErrorException("契约锁未认证");
        }
        ServiceAccountAuth serviceAccountAuth = company.getServiceAccountAuth();
        String phoneNumber = serviceAccountAuth.getPhoneNumber();
        ElectronicBillItem electronicBillItem = electronicBillItemRepository.findById(id).orElseThrow(() -> new YnceErrorException("账单不存在"));
        String thirdpartyNo = electronicBillItem.getThirdpartyNo();
        if (StringUtils.isNotEmpty(thirdpartyNo)) {
            Long contractId = Long.valueOf(thirdpartyNo);
            // 更新合同状态
            ContractDetailRequest detailRequest = new ContractDetailRequest(contractId);
            Contract detailResult = ContractLockUtil.getInstance().service(detailRequest, Contract.class);
            ContractStatus contractStatus = ContractStatus.valueOf(detailResult.getStatus());
            if (!contractStatus.equals(electronicBillItem.getStatus())) {
                electronicBillItem.setStatus(contractStatus);
                electronicBillItemRepository.save(electronicBillItem);
            }

            // 合同签署页面
            ContractPageRequest request = new ContractPageRequest(contractId,
                    new User(phoneNumber, ContactType.MOBILE.getValue()), "");
            ContractPageResult result = ContractLockUtil.getInstance().service(request, ContractPageResult.class);
            return result.getPageUrl();
        }

        ElectronicBill electronicBill = electronicBillItem.getElectronicBill();
        Contract draftContract = new Contract();
        // 业务ID 一个合同对应一个bizId，不能重复
        draftContract.setBizId(UUID.randomUUID().toString());
        // 是否发起合同
        draftContract.setSend(true);
        // 业务分类
        draftContract.setCategory(new Category(Long.valueOf(electronicBill.getClassifyCode())));
        // 签署方
        Signatory signatory = new Signatory();
        signatory.setTenantType(CompanyType.COMPANY.name());
        signatory.setTenantName(serviceAccountAuth.getName());
        signatory.setReceiver(new User(serviceAccountAuth.getPersonalName(),
                phoneNumber, ContactType.MOBILE.getValue()));
        // 签字
        Action signAction = new Action(ActionType.OPERATOR.getValue(), 0);
        signatory.addAction(signAction);
        // 盖章
        Action sealAction = new Action(ActionType.COMPANY.getValue(), 1);
        signatory.addAction(sealAction);
        draftContract.addSignatory(signatory);
        // 模板参数
        List<TemplateParam> templateParams = new ArrayList<>();
        List<ElectronicBillItemDaily> electronicBillItemDailys = electronicBillItem.getElectronicBillItemDaily();
        if (CollectionUtils.isNotEmpty(electronicBillItemDailys)) {
            List<Map<String, Object>> itemParamMapList = new ArrayList<>();
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy/M/d");
            for (int i = 0; i < electronicBillItemDailys.size(); i++) {
                ElectronicBillItemDaily electronicBillItemDaily = electronicBillItemDailys.get(i);
                Map<String, Object> itemParamMap = new HashMap<>();
                itemParamMap.put("sequence", i + 1);
                itemParamMap.put("billDate", electronicBillItemDaily.getBillDate().format(dateTimeFormatter));
                itemParamMap.put("abstracts", electronicBillItemDaily.getAbstracts());
                itemParamMap.put("amount", electronicBillItemDaily.getAmount());
                itemParamMap.put("balance", electronicBillItemDaily.getBalance());
                itemParamMap.put("remarks", electronicBillItemDaily.getRemarks());
                itemParamMapList.add(itemParamMap);
            }
            templateParams.add(new TemplateParam("每日明细", JSON.toJSONString(itemParamMapList)));
        }
        templateParams.add(new TemplateParam("客户名称", company.getName()));
        templateParams.add(new TemplateParam("联系地址", this.mergeArea(company)));
        templateParams.add(new TemplateParam("联系方式", company.getContactPhone()));
        LocalDate start = LocalDate.of(electronicBill.getBillYear(), electronicBill.getBillMonth(), 1);
        LocalDate end = start.plusMonths(1L).minusDays(1L);
        templateParams.add(new TemplateParam("对账期间",
                electronicBill.getBillYear() + "年" + electronicBill.getBillMonth() + "月1日-"
                        + electronicBill.getBillYear() + "年" + electronicBill.getBillMonth() + "月" + end.getDayOfMonth() + "日"));
        templateParams.add(new TemplateParam("上月末账户余额", electronicBillItem.getLastMonthEndBalance().toString()));
        templateParams.add(new TemplateParam("本月末账户余额", electronicBillItem.getCurrentMonthBalance().toString()));
        templateParams.add(new TemplateParam("本月累计订单", electronicBillItem.getCurrentMonthOrderAmount().toString()));
        templateParams.add(new TemplateParam("本月费用报销", electronicBillItem.getCurrentMonthCost().toString()));
        templateParams.add(new TemplateParam("本月累计发货", electronicBillItem.getCurrentMonthShippingAmount().toString()));
        // templateParams.add(new TemplateParam("累计未发货订单", electronicBillItem.getUnShippingAmount().toString()));
        templateParams.add(new TemplateParam("累计未发货订单", "  "));
        templateParams.add(new TemplateParam("发函单位", electronicBill.getCompany().getName()));
        templateParams.add(new TemplateParam("发函日期", LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日"))));
        draftContract.setTemplateParams(templateParams);
        // 生成合同
        ContractDraftRequest request = new ContractDraftRequest(draftContract);
        Contract result = ContractLockUtil.getInstance().service(request, Contract.class);

        // 保存合同号
        electronicBillItem.setThirdpartyNo(result.getId() + "");
        electronicBillItemRepository.saveAndFlush(electronicBillItem);

        // 合同签署页面
        ContractPageRequest r = new ContractPageRequest(result.getId(),
                new User(phoneNumber, ContactType.MOBILE.getValue()), "");
        ContractPageResult rs = ContractLockUtil.getInstance().service(r, ContractPageResult.class);
        return rs.getPageUrl();
    }

    @Override
    public CallVo inviteCallback(String inviteId, String result, String companyName, String registerNo) {
        log.info("契约锁邀请回调 inviteId: {}, result：{}, companyName: {}, registerNo: {}", inviteId, result, companyName, registerNo);
        // 根据邀请ID进行判断
        ServiceAccountAuth serviceAccountAuth = serviceAccountAuthRepository.findFirstByInviteId(inviteId);
        if (serviceAccountAuth == null) {
            log.error("契约锁邀请回调 找不到对应邀请记录：" + inviteId);
            return CallVo.builder().code(0).build();
        }
        if ("AGREE".equals(result)) {
            serviceAccountAuth.setInviteStatus(InviteStatus.HAVE_INVITED);
        } else if ("REJECT".equals(result)) {
            serviceAccountAuth.setInviteStatus(InviteStatus.INVITED_FAILURE);
        } else {
            serviceAccountAuth.setInviteStatus(InviteStatus.NOT_INVITED);
            log.error("契约锁邀请回调 错误的邀请结果：" + result);
        }
        serviceAccountAuthRepository.save(serviceAccountAuth);
        return CallVo.builder().code(0).build();
    }

    @Override
    public Long inviteSubCompany(ContractLockInviteSubCompanyDTO contractLockInviteSubCompanyDTO) {
        SubCompanyInviteRequest subCompanyInviteRequest = new SubCompanyInviteRequest();
        subCompanyInviteRequest.setCompanyName(contractLockInviteSubCompanyDTO.getCompanyName());
        subCompanyInviteRequest.setRegisterNo(contractLockInviteSubCompanyDTO.getRegisterNo());
        subCompanyInviteRequest.setCallbackUrl(ContractLockUtil.getThirdPartyUrl() + "/contract/call/back/invite");
        SubCompanyInviteResult subCompanyInviteResult = ContractLockUtil.getInstance().service(subCompanyInviteRequest, SubCompanyInviteResult.class);
        return subCompanyInviteResult.getInviteId();
    }

    @Override
    public void createEmployee(ContractLockCreateEmployeeDTO contractLockCreateEmployeeDTO) {
        // 添加员工
        EmployeeCreateRequest employeeCreateRequest = new EmployeeCreateRequest();
        employeeCreateRequest.setUser(new User(contractLockCreateEmployeeDTO.getUser().getName(),
                contractLockCreateEmployeeDTO.getUser().getContact(), contractLockCreateEmployeeDTO.getUser().getContactType()));
        employeeCreateRequest.setNumber(contractLockCreateEmployeeDTO.getNumber());
        employeeCreateRequest.setTenantName(contractLockCreateEmployeeDTO.getTenantName());
        // TODO 这里考虑是否该员工已经添加过
        Employee employee = ContractLockUtil.getInstance().service(employeeCreateRequest, Employee.class);
        // 为员工添加印章管理员
        this.roleManagement(contractLockCreateEmployeeDTO.getTenantName(), "SEAL_ADMIN", "ADD", Arrays.asList(employeeCreateRequest.getUser()));
        // 为员工添加模板管理员
        this.roleManagement(contractLockCreateEmployeeDTO.getTenantName(), "TEMPLATE_ADMIN", "ADD", Arrays.asList(employeeCreateRequest.getUser()));
    }

    @Override
    public void updateEmployee(ContractLockUpdateEmployeeDTO contractLockUpdateEmployeeDTO) {
        EmployeeUpdateRequest employee = new EmployeeUpdateRequest();
        employee.setUser(new User(contractLockUpdateEmployeeDTO.getUser().getName(),
                contractLockUpdateEmployeeDTO.getUser().getContact(), contractLockUpdateEmployeeDTO.getUser().getContactType()));
        employee.setNumber(contractLockUpdateEmployeeDTO.getNumber());
        employee.setTenantName(contractLockUpdateEmployeeDTO.getTenantName());
        try {
            ContractLockUtil.getInstance().service(employee, SdkResponse.class);
        } catch (Exception exception) {
            if (StringUtils.isNotBlank(exception.getMessage()) && exception.getMessage().contains("EMPLOYEE NAME NOT MATCH")) {
                throw new YnceErrorException(YnceError.YNCE_216002.getStatus(), "该手机号对应用户已实名认证，请确保修改的姓名与真实姓名一致！");
            } else {
                throw exception;
            }
        }
    }

    /**
     * 契约锁-管理员工角色
     *
     * @param tenantName 子公司名称
     * @param role       角色成员：SEAL_ADMIN（“印章管理员”），TEMPLATE_ADMIN（“模板管理员”）
     * @param operate    操作方式，ADD（“添加”），REMOVE（“移除”），默认为添加
     * @param users      编辑的成员列表，必须为已注册的企业员工
     * @author huabiao
     * @create 2023/12/25  12:22
     */
    private void roleManagement(String tenantName, String role, String operate, List<User> users) {
        RoleManagementRequest roleManagementRequest = new RoleManagementRequest(role, operate, users);
        roleManagementRequest.setTenantName(tenantName);
        ContractLockUtil.getInstance().service(roleManagementRequest, SdkResponse.class);
    }


    private String mergeArea(Company company) {
        String addr = "";
        if (company.getCity() == null || !org.springframework.util.StringUtils.hasText(company.getCity().getName())) {
            if (company.getOfficeAddress() != null) {
                addr = company.getOfficeAddress();
            }
        } else {
            addr = company.getProvince().getName() + company.getCity().getName();
            if (company.getArea() != null && org.springframework.util.StringUtils.hasText(company.getArea().getName())) {
                addr += company.getArea().getName();
                if (company.getStreet() != null && org.springframework.util.StringUtils.hasText(company.getStreet().getName())) {
                    addr += company.getStreet().getName();
                    if (company.getOfficeAddress() != null) {
                        addr += company.getOfficeAddress();
                    }
                }
            }
        }
        return addr;
    }
}
