/**
 * BladeX Commercial License Agreement
 * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
 * <p>
 * Use of this software is governed by the Commercial License Agreement
 * obtained after purchasing a license from BladeX.
 * <p>
 * 1. This software is for development use only under a valid license
 * from BladeX.
 * <p>
 * 2. Redistribution of this software's source code to any third party
 * without a commercial license is strictly prohibited.
 * <p>
 * 3. Licensees may copyright their own code but cannot use segments
 * from this software for such purposes. Copyright of this software
 * remains with BladeX.
 * <p>
 * Using this software signifies agreement to this License, and the software
 * must not be used for illegal purposes.
 * <p>
 * Author: Chill Zhuang (bladejava@qq.com)
 */
package org.springblade.transport.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.mp.base.BaseServiceImpl;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.utils.BeanUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.system.cache.UserCache;
import org.springblade.system.pojo.entity.Dept;
import org.springblade.transport.excel.ProjectApplyExcel;
import org.springblade.transport.mapper.ProjectApplyMapper;
import org.springblade.transport.pojo.entity.ProjectApply;
import org.springblade.transport.pojo.vo.ProjectApplyVO;
import org.springblade.transport.service.IProjectApplyService;
import org.springblade.transport.support.TransportBusinessSupport;
import org.springblade.transport.wrapper.ProjectApplyWrapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Objects;

/**
 * 项目立项 服务实现类
 *
 * @author Chill
 */
@Service
public class ProjectApplyServiceImpl extends BaseServiceImpl<ProjectApplyMapper, ProjectApply> implements IProjectApplyService {

	private static final String STATUS_DRAFT = "draft";
	private static final String STATUS_REVIEWING = "reviewing";
	private static final String STATUS_WITHDRAWN = "withdrawn";
	private static final String STATUS_APPROVED = "approved";
	private static final String STATUS_REJECTED = "rejected";
	private static final String STATUS_CHANGE_REVIEWING = "change_reviewing";
	private static final String STATUS_VOIDED = "voided";
	private static final String EFFECTIVE_TEMPORARY = "temporary";
	private static final String EFFECTIVE_FORMAL = "formal";
	private static final String CHANGE_TYPE_PROJECT = "项目变更";
	private static final String CHANGE_TYPE_RECORD = "项目备案调整";
	private static final DateTimeFormatter CODE_DATE = DateTimeFormatter.ofPattern("yyyyMMdd");
	private static final DateTimeFormatter PROJECT_YEAR = DateTimeFormatter.ofPattern("yyyy");

	@Override
	public IPage<ProjectApplyVO> selectProjectApplyPage(IPage<ProjectApply> page, ProjectApplyVO projectApply) {
		IPage<ProjectApply> entityPage = page(page, buildQuery(projectApply));
		IPage<ProjectApplyVO> voPage = ProjectApplyWrapper.build().pageVO(entityPage);
		voPage.getRecords().forEach(this::fillReadonly);
		return voPage;
	}

	@Override
	public ProjectApplyVO detail(Long id) {
		ProjectApplyVO projectApplyVO = ProjectApplyWrapper.build().entityVO(loadExists(id));
		normalizeOptionalAmounts(projectApplyVO);
		fillReadonly(projectApplyVO);
		return projectApplyVO;
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean saveDraft(ProjectApply projectApply) {
		prepare(projectApply);
		if (Func.isNotEmpty(projectApply.getId())) {
			loadEditable(projectApply.getId(), true);
		}
		prepareCreateOrUpdate(projectApply);
		projectApply.setApprovalStatus(resolveEditableStatus(projectApply));
		return saveOrUpdate(projectApply);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean submit(ProjectApply projectApply) {
		prepare(projectApply);
		validateSubmit(projectApply);
		if (Func.isNotEmpty(projectApply.getId())) {
			loadEditable(projectApply.getId(), true);
		}
		prepareCreateOrUpdate(projectApply);
		projectApply.setApprovalStatus(STATUS_REVIEWING);
		projectApply.setCurrentNode(resolveApprovalNode(projectApply));
		projectApply.setCurrentProcessor("待处理");
		return saveOrUpdate(projectApply);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean submitApproval(Long id) {
		ProjectApply projectApply = loadEditable(id, true);
		validateSubmit(projectApply);
		projectApply.setApprovalStatus(STATUS_REVIEWING);
		projectApply.setCurrentNode(resolveApprovalNode(projectApply));
		projectApply.setCurrentProcessor("待处理");
		return updateById(projectApply);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean approve(Long id) {
		ProjectApply projectApply = loadExists(id);
		if (!List.of(STATUS_REVIEWING, STATUS_CHANGE_REVIEWING).contains(projectApply.getApprovalStatus())) {
			throw new ServiceException("当前状态不允许审批通过");
		}
		projectApply.setApprovalStatus(STATUS_CHANGE_REVIEWING.equals(projectApply.getApprovalStatus()) ? "change_approved" : STATUS_APPROVED);
		projectApply.setEffectiveType(EFFECTIVE_FORMAL);
		projectApply.setCurrentNode("审批通过");
		projectApply.setCurrentProcessor(AuthUtil.getUserName());
		projectApply.setApprovedTime(LocalDateTime.now());
		return updateById(projectApply);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean reject(Long id) {
		ProjectApply projectApply = loadExists(id);
		if (!List.of(STATUS_REVIEWING, STATUS_CHANGE_REVIEWING).contains(projectApply.getApprovalStatus())) {
			throw new ServiceException("当前状态不允许驳回");
		}
		projectApply.setApprovalStatus(STATUS_CHANGE_REVIEWING.equals(projectApply.getApprovalStatus()) ? "change_rejected" : STATUS_REJECTED);
		projectApply.setCurrentNode("已驳回");
		projectApply.setCurrentProcessor(AuthUtil.getUserName());
		return updateById(projectApply);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean withdraw(Long id) {
		ProjectApply projectApply = loadExists(id);
		if (!Objects.equals(projectApply.getApprovalStatus(), STATUS_REVIEWING)) {
			throw new ServiceException("仅审批中项目允许撤回");
		}
		projectApply.setApprovalStatus(STATUS_WITHDRAWN);
		projectApply.setCurrentNode("已撤回");
		projectApply.setCurrentProcessor(AuthUtil.getUserName());
		return updateById(projectApply);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean voidProject(Long id, String reason) {
		ProjectApply projectApply = loadExists(id);
		if (!Objects.equals(projectApply.getApprovalStatus(), STATUS_APPROVED)) {
			throw new ServiceException("仅审批通过项目允许作废");
		}
		projectApply.setApprovalStatus(STATUS_VOIDED);
		projectApply.setVoidReason(TransportBusinessSupport.trimToNull(reason));
		projectApply.setCurrentNode("已作废");
		projectApply.setCurrentProcessor(AuthUtil.getUserName());
		return updateById(projectApply);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean saveChange(ProjectApplyVO projectApply) {
		ProjectApply oldRecord = loadChangeable(projectApply.getId());
		prepare(projectApply);
		copyChangeFields(oldRecord, projectApply);
		validateSubmit(oldRecord);
		validateChangeLength(oldRecord);
		return updateById(oldRecord);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean submitChange(ProjectApplyVO projectApply) {
		ProjectApply oldRecord = loadChangeable(projectApply.getId());
		prepare(projectApply);
		copyChangeFields(oldRecord, projectApply);
		validateSubmit(oldRecord);
		validateChange(oldRecord, projectApply.getChangeType());
		oldRecord.setApprovalStatus(STATUS_CHANGE_REVIEWING);
		oldRecord.setCurrentNode(projectApply.getChangeType() + "审批");
		oldRecord.setCurrentProcessor("待处理");
		return updateById(oldRecord);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean startChange(Long id, String changeContent, String changeReason) {
		ProjectApply projectApply = loadExists(id);
		validateChangeable(projectApply);
		if (!canCurrentUserOperate(projectApply)) {
			throw new ServiceException("无权操作其他组织项目立项");
		}
		TransportBusinessSupport.validateRequired(changeContent, "请输入变更内容");
		TransportBusinessSupport.validateRequired(changeReason, "请输入变更原因");
		projectApply.setApprovalStatus(STATUS_CHANGE_REVIEWING);
		projectApply.setChangeContent(TransportBusinessSupport.trimToNull(changeContent));
		projectApply.setChangeReason(TransportBusinessSupport.trimToNull(changeReason));
		projectApply.setCurrentNode("项目变更审批");
		projectApply.setCurrentProcessor("待处理");
		return updateById(projectApply);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean removeDraft(String ids) {
		List<Long> idList = Func.toLongList(ids);
		if (Func.isEmpty(idList)) {
			throw new ServiceException("请选择需要删除的数据");
		}
		for (ProjectApply projectApply : listByIds(idList)) {
			if (!List.of(STATUS_DRAFT, STATUS_WITHDRAWN, STATUS_REJECTED).contains(projectApply.getApprovalStatus())) {
				throw new ServiceException("仅草稿、已撤回、已驳回项目允许删除");
			}
		}
		return deleteLogic(idList);
	}

	@Override
	public List<ProjectApplyExcel> exportProjectApply(ProjectApplyVO projectApply, String ids) {
		LambdaQueryWrapper<ProjectApply> queryWrapper = buildQuery(projectApply);
		if (Func.isNotEmpty(ids)) {
			queryWrapper.in(ProjectApply::getId, Func.toLongList(ids));
		}
		return list(queryWrapper).stream().map(record -> {
			ProjectApplyExcel excel = Objects.requireNonNull(BeanUtil.copyProperties(record, ProjectApplyExcel.class));
			excel.setFundLimit(scale(record.getFundLimit()));
			excel.setReceivableLimit(scale(record.getReceivableLimit()));
			excel.setProjectScale(scale(record.getProjectScale()));
			excel.setEstimatedProfit(scale(record.getEstimatedProfit()));
			excel.setFundDemand(scale(record.getFundDemand()));
			excel.setCreateUserName(UserCache.getUserRealName(record.getCreateUser()));
			excel.setUpdateUserName(UserCache.getUserRealName(record.getUpdateUser()));
			return excel;
		}).toList();
	}

	private LambdaQueryWrapper<ProjectApply> buildQuery(ProjectApplyVO projectApply) {
		TransportBusinessSupport.validateAllDept(projectApply.getAllDept(), "项目立项");
		LambdaQueryWrapper<ProjectApply> queryWrapper = Wrappers.<ProjectApply>lambdaQuery().eq(ProjectApply::getIsDeleted, 0);
		if (!Objects.equals(projectApply.getAllDept(), 1)) {
			Long currentDeptId = TransportBusinessSupport.currentDeptId("项目立项");
			Long currentUserId = AuthUtil.getUserId();
			queryWrapper.and(wrapper -> wrapper.eq(ProjectApply::getUndertakeDeptId, currentDeptId)
				.or().eq(ProjectApply::getCreateUser, currentUserId)
				.or().eq(ProjectApply::getHandlerUserId, currentUserId)
				.or().eq(ProjectApply::getPrincipalUserId, currentUserId));
		} else if (Func.isNotEmpty(projectApply.getUndertakeDeptId())) {
			queryWrapper.eq(ProjectApply::getUndertakeDeptId, projectApply.getUndertakeDeptId());
		}
		queryWrapper.like(Func.isNotEmpty(projectApply.getApplyNo()), ProjectApply::getApplyNo, projectApply.getApplyNo())
			.like(Func.isNotEmpty(projectApply.getProjectName()), ProjectApply::getProjectName, projectApply.getProjectName())
			.like(Func.isNotEmpty(projectApply.getUndertakeDeptName()), ProjectApply::getUndertakeDeptName, projectApply.getUndertakeDeptName())
			.like(Func.isNotEmpty(projectApply.getPrincipalUserName()), ProjectApply::getPrincipalUserName, projectApply.getPrincipalUserName())
			.eq(Func.isNotEmpty(projectApply.getProjectType()), ProjectApply::getProjectType, projectApply.getProjectType())
			.eq(Func.isNotEmpty(projectApply.getApprovalStatus()), ProjectApply::getApprovalStatus, projectApply.getApprovalStatus())
			.eq(Func.isNotEmpty(projectApply.getEffectiveType()), ProjectApply::getEffectiveType, projectApply.getEffectiveType())
			.orderByDesc(ProjectApply::getCreateTime);
		if (Boolean.TRUE.equals(projectApply.getTemporaryCreditLimitSelectable())) {
			queryWrapper.eq(ProjectApply::getEffectiveType, EFFECTIVE_FORMAL)
				.in(ProjectApply::getApprovalStatus, STATUS_APPROVED, "change_approved");
		}
		return queryWrapper;
	}

	private void prepareCreateOrUpdate(ProjectApply projectApply) {
		if (Func.isEmpty(projectApply.getId())) {
			if (Func.isEmpty(projectApply.getApplyNo())) {
				projectApply.setApplyNo(nextApplyNo());
			}
			if (Func.isEmpty(projectApply.getProjectCode())) {
				projectApply.setProjectCode(nextProjectCode());
			}
			if (Func.isEmpty(projectApply.getUndertakeDeptId())) {
				Dept dept = TransportBusinessSupport.currentDept("项目立项");
				projectApply.setUndertakeDeptId(dept.getId());
				projectApply.setUndertakeDeptName(dept.getDeptName());
			}
			if (Func.isEmpty(projectApply.getBusinessDeptId())) {
				projectApply.setBusinessDeptId(projectApply.getUndertakeDeptId());
				projectApply.setBusinessDeptName(projectApply.getUndertakeDeptName());
			}
			if (Func.isEmpty(projectApply.getHandlerUserId())) {
				projectApply.setHandlerUserId(AuthUtil.getUserId());
				projectApply.setHandlerUserName(AuthUtil.getUserName());
			}
			if (Func.isEmpty(projectApply.getPrincipalUserId())) {
				projectApply.setPrincipalUserId(AuthUtil.getUserId());
				projectApply.setPrincipalUserName(AuthUtil.getUserName());
			}
		} else {
			ProjectApply oldRecord = loadEditable(projectApply.getId(), true);
			projectApply.setApplyNo(oldRecord.getApplyNo());
			projectApply.setProjectCode(oldRecord.getProjectCode());
			projectApply.setUndertakeDeptId(oldRecord.getUndertakeDeptId());
			projectApply.setUndertakeDeptName(oldRecord.getUndertakeDeptName());
		}
		if (Func.isEmpty(projectApply.getEffectiveType())) {
			projectApply.setEffectiveType(EFFECTIVE_TEMPORARY);
		}
	}

	private void prepare(ProjectApply projectApply) {
		projectApply.setProjectType(TransportBusinessSupport.trimToNull(projectApply.getProjectType()));
		projectApply.setProjectName(TransportBusinessSupport.trimToNull(projectApply.getProjectName()));
		projectApply.setProjectShortName(TransportBusinessSupport.trimToNull(projectApply.getProjectShortName()));
		projectApply.setBusinessDeptName(TransportBusinessSupport.trimToNull(projectApply.getBusinessDeptName()));
		projectApply.setUndertakeDeptName(TransportBusinessSupport.trimToNull(projectApply.getUndertakeDeptName()));
		projectApply.setProjectSource(TransportBusinessSupport.trimToNull(projectApply.getProjectSource()));
		projectApply.setSourceRemark(TransportBusinessSupport.trimToNull(projectApply.getSourceRemark()));
		projectApply.setCargoType(TransportBusinessSupport.trimToNull(projectApply.getCargoType()));
		projectApply.setCargoQuantity(TransportBusinessSupport.trimToNull(projectApply.getCargoQuantity()));
		projectApply.setTransportRoute(TransportBusinessSupport.trimToNull(projectApply.getTransportRoute()));
		projectApply.setTransportType(TransportBusinessSupport.trimToNull(projectApply.getTransportType()));
		projectApply.setBusinessType(TransportBusinessSupport.trimToNull(projectApply.getBusinessType()));
		normalizeOptionalAmounts(projectApply);
		projectApply.setSettlementMode(TransportBusinessSupport.trimToNull(projectApply.getSettlementMode()));
		projectApply.setHandlerUserName(TransportBusinessSupport.trimToNull(projectApply.getHandlerUserName()));
		projectApply.setPrincipalUserName(TransportBusinessSupport.trimToNull(projectApply.getPrincipalUserName()));
		projectApply.setCustomerNames(TransportBusinessSupport.trimToNull(projectApply.getCustomerNames()));
		projectApply.setCarrierNames(TransportBusinessSupport.trimToNull(projectApply.getCarrierNames()));
		projectApply.setSituationRemark(TransportBusinessSupport.trimToNull(projectApply.getSituationRemark()));
	}

	private void normalizeOptionalAmounts(ProjectApply projectApply) {
		if (projectApply.getProjectScale() != null && projectApply.getProjectScale().signum() < 0) {
			projectApply.setProjectScale(null);
		}
		if (projectApply.getEstimatedProfit() != null && projectApply.getEstimatedProfit().signum() < 0) {
			projectApply.setEstimatedProfit(null);
		}
		if (projectApply.getFundDemand() != null && projectApply.getFundDemand().signum() < 0) {
			projectApply.setFundDemand(null);
		}
	}

	private void copyChangeFields(ProjectApply target, ProjectApply source) {
		target.setFundLimit(source.getFundLimit());
		target.setReceivableLimit(source.getReceivableLimit());
		target.setReceivableDays(source.getReceivableDays());
		target.setPaymentDays(source.getPaymentDays());
		target.setCargoType(source.getCargoType());
		target.setCargoQuantity(source.getCargoQuantity());
		target.setBusinessStartDate(source.getBusinessStartDate());
		target.setBusinessEndDate(source.getBusinessEndDate());
		target.setTransportRoute(source.getTransportRoute());
		target.setTransportType(source.getTransportType());
		target.setBusinessType(source.getBusinessType());
		target.setProjectScale(source.getProjectScale());
		target.setSettlementMode(source.getSettlementMode());
		target.setEstimatedProfit(source.getEstimatedProfit());
		target.setFundDemand(source.getFundDemand());
		target.setHandlerUserId(source.getHandlerUserId());
		target.setHandlerUserName(source.getHandlerUserName());
		target.setPrincipalUserId(source.getPrincipalUserId());
		target.setPrincipalUserName(source.getPrincipalUserName());
		target.setCustomerNames(source.getCustomerNames());
		target.setCarrierNames(source.getCarrierNames());
		target.setCustomerJson(source.getCustomerJson());
		target.setCarrierJson(source.getCarrierJson());
		target.setSituationRemark(source.getSituationRemark());
		target.setAttachmentsJson(source.getAttachmentsJson());
		target.setChangeContent(TransportBusinessSupport.trimToNull(source.getChangeContent()));
		target.setChangeReason(TransportBusinessSupport.trimToNull(source.getChangeReason()));
	}

	private void validateDraft(ProjectApply projectApply) {
		TransportBusinessSupport.validateRequired(projectApply.getProjectType(), "请选择项目类型");
		TransportBusinessSupport.validateRequired(projectApply.getProjectName(), "请输入项目名称");
		validateUniqueName(projectApply);
		validateLength(projectApply);
	}

	private void validateSubmit(ProjectApply projectApply) {
		validateDraft(projectApply);
		TransportBusinessSupport.validateRequired(projectApply.getProjectShortName(), "请输入项目简称");
		TransportBusinessSupport.validateRequired(projectApply.getBusinessDeptName(), "请选择业务部门");
		TransportBusinessSupport.validateRequired(projectApply.getUndertakeDeptName(), "请选择承办部门");
		TransportBusinessSupport.validateRequired(projectApply.getCustomerNames(), "请选择客户");
		TransportBusinessSupport.validateRequired(projectApply.getCarrierNames(), "请选择下游承运商");
		validateAmount(projectApply.getFundLimit(), "项目资金使用额度", false);
		validateAmount(projectApply.getReceivableLimit(), "项目应收账款额度", false);
		if (projectApply.getReceivableLimit() != null && projectApply.getFundLimit() != null && projectApply.getReceivableLimit().compareTo(projectApply.getFundLimit()) > 0) {
			throw new ServiceException("项目应收账款额度不能超过项目资金使用额度");
		}
		validateDays(projectApply.getReceivableDays(), "应收账款回款期限");
		validateDays(projectApply.getPaymentDays(), "回款账期");
		validateAmount(projectApply.getProjectScale(), "项目规模", true);
		validateAmount(projectApply.getEstimatedProfit(), "预计利润", true);
		validateAmount(projectApply.getFundDemand(), "资金需求", true);
		TransportBusinessSupport.validateDateRange(projectApply.getBusinessStartDate(), projectApply.getBusinessEndDate(), "业务周期开始日期不能晚于结束日期");
		if (Func.isEmpty(projectApply.getHandlerUserId()) || Func.isEmpty(projectApply.getPrincipalUserId())) {
			throw new ServiceException("项目经办人和项目负责人不能为空");
		}
	}

	private void validateUniqueName(ProjectApply projectApply) {
		Long count = count(Wrappers.<ProjectApply>lambdaQuery()
			.eq(ProjectApply::getIsDeleted, 0)
			.eq(ProjectApply::getProjectName, projectApply.getProjectName())
			.ne(Func.isNotEmpty(projectApply.getId()), ProjectApply::getId, projectApply.getId()));
		if (count > 0) {
			throw new ServiceException("项目名称已存在");
		}
	}

	private void validateLength(ProjectApply projectApply) {
		TransportBusinessSupport.validateLength(projectApply.getProjectName(), 50, "项目名称不能超过50个字");
		TransportBusinessSupport.validateLength(projectApply.getProjectShortName(), 20, "项目简称不能超过20个字");
		TransportBusinessSupport.validateLength(projectApply.getSourceRemark(), 300, "项目由来说明不能超过300个字");
		TransportBusinessSupport.validateLength(projectApply.getCargoQuantity(), 100, "预估货物数量不能超过100个字");
		TransportBusinessSupport.validateLength(projectApply.getTransportRoute(), 100, "运输线路不能超过100个字");
	}

	private void validateChange(ProjectApply projectApply, String changeType) {
		if (!List.of(CHANGE_TYPE_PROJECT, CHANGE_TYPE_RECORD).contains(changeType)) {
			throw new ServiceException("请选择变更类型");
		}
		TransportBusinessSupport.validateRequired(projectApply.getChangeContent(), "请输入变更内容");
		TransportBusinessSupport.validateRequired(projectApply.getChangeReason(), "请输入变更原因");
		validateChangeLength(projectApply);
	}

	private void validateChangeLength(ProjectApply projectApply) {
		TransportBusinessSupport.validateLength(projectApply.getChangeContent(), 2000, "变更内容不能超过2000个字");
		TransportBusinessSupport.validateLength(projectApply.getChangeReason(), 2000, "变更原因不能超过2000个字");
	}

	private void validateAmount(BigDecimal value, String label, boolean nullable) {
		if (!nullable && value == null) {
			throw new ServiceException(label + "不能为空");
		}
		if (value == null) {
			return;
		}
		if (value.compareTo(BigDecimal.ZERO) < 0) {
			throw new ServiceException(label + "不能小于0");
		}
		if (value.scale() > 2) {
			throw new ServiceException(label + "最多保留2位小数");
		}
	}

	private void validateDays(Integer value, String label) {
		if (value == null) {
			throw new ServiceException(label + "不能为空");
		}
		if (value < 0 || value > 999) {
			throw new ServiceException(label + "范围为0到999天");
		}
	}

	private ProjectApply loadExists(Long id) {
		if (Func.isEmpty(id)) {
			throw new ServiceException("主键不能为空");
		}
		ProjectApply projectApply = getById(id);
		if (Func.isEmpty(projectApply) || Objects.equals(projectApply.getIsDeleted(), 1)) {
			throw new ServiceException("项目立项不存在");
		}
		return projectApply;
	}

	private ProjectApply loadEditable(Long id, boolean assertDept) {
		ProjectApply projectApply = loadExists(id);
		if (assertDept && !canCurrentUserOperate(projectApply)) {
			throw new ServiceException("无权操作其他组织项目立项");
		}
		if (!List.of(STATUS_DRAFT, STATUS_WITHDRAWN, STATUS_REJECTED, "change_rejected").contains(projectApply.getApprovalStatus())) {
			throw new ServiceException("当前状态不允许编辑");
		}
		return projectApply;
	}

	private ProjectApply loadChangeable(Long id) {
		ProjectApply projectApply = loadExists(id);
		validateChangeable(projectApply);
		if (!canCurrentUserOperate(projectApply)) {
			throw new ServiceException("无权操作其他组织项目立项");
		}
		return projectApply;
	}

	private void validateChangeable(ProjectApply projectApply) {
		if (!Objects.equals(projectApply.getApprovalStatus(), STATUS_APPROVED) || !Objects.equals(projectApply.getEffectiveType(), EFFECTIVE_FORMAL)) {
			throw new ServiceException("仅正式生效且审批通过的项目允许发起变更");
		}
	}

	private String resolveEditableStatus(ProjectApply projectApply) {
		if (Func.isEmpty(projectApply.getApprovalStatus())) {
			return STATUS_DRAFT;
		}
		return List.of(STATUS_WITHDRAWN, STATUS_REJECTED, "change_rejected").contains(projectApply.getApprovalStatus()) ? projectApply.getApprovalStatus() : STATUS_DRAFT;
	}

	private String resolveApprovalNode(ProjectApply projectApply) {
		if ("重大项目".equals(projectApply.getProjectType()) && projectApply.getFundLimit() != null && projectApply.getFundLimit().compareTo(new BigDecimal("8000")) > 0) {
			return "物流领导审批";
		}
		return "项目立项审批";
	}

	private String nextApplyNo() {
		return "LXAP" + LocalDate.now().format(CODE_DATE) + String.format("%06d", count() + 1);
	}

	private String nextProjectCode() {
		return "LX-" + LocalDate.now().format(PROJECT_YEAR) + "-" + String.format("%06d", count() + 1);
	}

	private BigDecimal scale(BigDecimal value) {
		return value == null ? null : value.setScale(2, RoundingMode.HALF_UP);
	}

	private void fillReadonly(ProjectApplyVO projectApplyVO) {
		projectApplyVO.setReadonly(!canCurrentUserOperate(projectApplyVO));
	}

	private boolean canCurrentUserOperate(ProjectApply projectApply) {
		if (AuthUtil.isAdministrator()) {
			return true;
		}
		Long currentDeptId = TransportBusinessSupport.currentDeptId("项目立项");
		Long currentUserId = AuthUtil.getUserId();
		return Objects.equals(projectApply.getUndertakeDeptId(), currentDeptId)
			|| Objects.equals(projectApply.getCreateUser(), currentUserId)
			|| Objects.equals(projectApply.getHandlerUserId(), currentUserId)
			|| Objects.equals(projectApply.getPrincipalUserId(), currentUserId);
	}

}
