/**
 * 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.tool.jackson.JsonUtil;
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.WaybillExcel;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import org.springblade.transport.mapper.WaybillMapper;
import org.springblade.transport.pojo.entity.Waybill;
import org.springblade.transport.pojo.vo.BusinessRemoveResultVO;
import org.springblade.transport.pojo.vo.WaybillVO;
import org.springblade.transport.service.IWaybillService;
import org.springblade.transport.support.TransportBusinessSupport;
import org.springblade.transport.wrapper.WaybillWrapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 运单管理 服务实现类
 *
 * @author Chill
 */
@Service
public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill> implements IWaybillService {

	@Override
	public IPage<WaybillVO> selectWaybillPage(IPage<Waybill> page, WaybillVO waybill) {
		IPage<Waybill> entityPage = page(page, buildQuery(waybill));
		return WaybillWrapper.build().pageVO(entityPage);
	}

	@Override
	public WaybillVO detail(Long id) {
		return WaybillWrapper.build().entityVO(loadEditable(id, false));
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean submit(Waybill waybill) {
		boolean created = Func.isEmpty(waybill.getId());
		if (!created) {
			Waybill oldRecord = loadEditable(waybill.getId(), true);
			waybill.setWaybillNo(oldRecord.getWaybillNo());
			waybill.setDeptId(oldRecord.getDeptId());
			waybill.setDeptName(oldRecord.getDeptName());
		}
		prepare(waybill);
		if (created && Func.isEmpty(waybill.getWaybillNo())) {
			waybill.setWaybillNo(nextCode());
		}
		validate(waybill);
		return saveOrUpdate(waybill);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public BusinessRemoveResultVO removeWaybill(String ids) {
		List<Long> idList = Func.toLongList(ids);
		if (Func.isEmpty(idList)) {
			throw new ServiceException("请选择需要删除的数据");
		}
		BusinessRemoveResultVO result = new BusinessRemoveResultVO();
		List<Long> deleteIdList = new ArrayList<>();
		for (Waybill waybill : listByIds(idList)) {
			TransportBusinessSupport.assertCurrentDept(waybill.getDeptId(), "运单管理");
			if (shouldSkipDelete(waybill)) {
				result.setSkippedCount(result.getSkippedCount() + 1);
				result.getSkippedCodes().add(waybill.getWaybillNo());
				continue;
			}
			deleteIdList.add(waybill.getId());
		}
		if (Func.isNotEmpty(deleteIdList)) {
			deleteLogic(deleteIdList);
		}
		result.setSuccessCount(deleteIdList.size());
		return result;
	}

	@Override
	public List<WaybillExcel> exportWaybill(WaybillVO waybill, String ids) {
		LambdaQueryWrapper<Waybill> queryWrapper = buildQuery(waybill);
		if (Func.isNotEmpty(ids)) {
			queryWrapper.in(Waybill::getId, Func.toLongList(ids));
		}
		return list(queryWrapper).stream().map(record -> {
			WaybillExcel excel = new WaybillExcel();
			BeanUtil.copyProperties(record, excel);
			excel.setCreateUserName(UserCache.getUserRealName(record.getCreateUser()));
			excel.setUpdateUserName(UserCache.getUserRealName(record.getUpdateUser()));
			return excel;
		}).toList();
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public List<WaybillExcel> importWaybill(List<WaybillExcel> data) {
		if (Func.isEmpty(data)) {
			throw new ServiceException("导入数据不能为空");
		}
		List<WaybillExcel> errorList = new ArrayList<>();
		for (int index = 0; index < data.size(); index++) {
			WaybillExcel excel = data.get(index);
			try {
				Waybill waybill = Objects.requireNonNull(BeanUtil.copyProperties(excel, Waybill.class));
				waybill.setDataSource("批量导入");
				waybill.setCarrierJson(buildImportCarrierJson(waybill));
				submit(waybill);
			} catch (Exception exception) {
				excel.setErrorMessage("第" + (index + 2) + "行：" + exception.getMessage());
				errorList.add(excel);
			}
		}
		return errorList;
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public WaybillVO copy(Long id) {
		Waybill source = loadEditable(id, true);
		Waybill target = new Waybill();
		target.setProjectId(source.getProjectId());
		target.setProjectName(source.getProjectName());
		target.setContractId(source.getContractId());
		target.setContractName(source.getContractName());
		target.setCustomerName(source.getCustomerName());
		target.setTransportType(source.getTransportType());
		target.setCargoName(source.getCargoName());
		target.setCargoType(source.getCargoType());
		target.setSpecification(source.getSpecification());
		target.setModel(source.getModel());
		target.setQuantity(source.getQuantity());
		target.setQuantityUnit(source.getQuantityUnit());
		target.setDepartureAddressId(source.getDepartureAddressId());
		target.setDepartureName(source.getDepartureName());
		target.setDepartureAddress(source.getDepartureAddress());
		target.setDepartureContact(source.getDepartureContact());
		target.setDeparturePhone(source.getDeparturePhone());
		target.setArrivalAddressId(source.getArrivalAddressId());
		target.setArrivalName(source.getArrivalName());
		target.setArrivalAddress(source.getArrivalAddress());
		target.setArrivalContact(source.getArrivalContact());
		target.setArrivalPhone(source.getArrivalPhone());
		target.setTaskEntryMode(source.getTaskEntryMode());
		target.setCarrierType(source.getCarrierType());
		target.setCarrierId(source.getCarrierId());
		target.setCarrierName(source.getCarrierName());
		target.setDriverId(source.getDriverId());
		target.setDriverName(source.getDriverName());
		target.setDriverPhone(source.getDriverPhone());
		target.setVehicleNo(source.getVehicleNo());
		target.setTrailerVehicleNo(source.getTrailerVehicleNo());
		target.setEscortName(source.getEscortName());
		target.setEscortPhone(source.getEscortPhone());
		target.setMileage(source.getMileage());
		target.setEstimatedStartTime(source.getEstimatedStartTime());
		target.setEstimatedEndTime(source.getEstimatedEndTime());
		target.setUnitPrice(source.getUnitPrice());
		target.setPriceUnit(source.getPriceUnit());
		target.setOtherFeeTotal(source.getOtherFeeTotal());
		target.setTaskRemark(source.getTaskRemark());
		target.setOriginalNo(source.getOriginalNo());
		target.setBusinessStatus(source.getBusinessStatus());
		target.setDataSource(source.getDataSource());
		target.setStartDate(source.getStartDate());
		target.setEndDate(source.getEndDate());
		target.setPlanId(source.getPlanId());
		target.setPlanName(source.getPlanName());
		target.setMasterNo(source.getMasterNo());
		target.setLoadingNo(source.getLoadingNo());
		target.setBatchNo(source.getBatchNo());
		target.setRelationNo(source.getRelationNo());
		target.setCurrentProcessNode(source.getCurrentProcessNode());
		target.setGoodsJson(source.getGoodsJson());
		target.setCarrierJson(source.getCarrierJson());
		target.setTaskInfoJson(source.getTaskInfoJson());
		target.setProcessJson(source.getProcessJson());
		target.setFreightJson(source.getFreightJson());
		target.setAttachmentsJson(source.getAttachmentsJson());
		target.setRemark(source.getRemark());
		target.setBusinessStatus("pending");
		target.setWaybillNo(nextCode());
		prepare(target);
		validate(target);
		save(target);
		return detail(target.getId());
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean cancel(Long id) {
		Waybill waybill = loadEditable(id, true);
		if ("completed".equals(waybill.getBusinessStatus()) || "cancelled".equals(waybill.getBusinessStatus())) {
			throw new ServiceException("当前状态不允许取消");
		}
		waybill.setBusinessStatus("cancelled");
		return updateById(waybill);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean reassign(Long id) {
		Waybill waybill = loadEditable(id, true);
		if (!"pending".equals(waybill.getBusinessStatus())) {
			throw new ServiceException("仅待执行运单允许重新派单");
		}
		waybill.setBusinessStatus("pending");
		return updateById(waybill);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean complete(Long id) {
		Waybill waybill = loadEditable(id, true);
		if ("completed".equals(waybill.getBusinessStatus()) || "cancelled".equals(waybill.getBusinessStatus())) {
			throw new ServiceException("当前状态不允许完成");
		}
		waybill.setBusinessStatus("completed");
		return updateById(waybill);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public BusinessRemoveResultVO batchComplete(String ids) {
		List<Long> idList = Func.toLongList(ids);
		if (Func.isEmpty(idList)) {
			throw new ServiceException("请选择需要完成的数据");
		}
		BusinessRemoveResultVO result = new BusinessRemoveResultVO();
		for (Waybill waybill : listByIds(idList)) {
			try {
				complete(waybill.getId());
				result.setSuccessCount(result.getSuccessCount() + 1);
			} catch (Exception exception) {
				result.setSkippedCount(result.getSkippedCount() + 1);
				result.getSkippedCodes().add(waybill.getWaybillNo());
			}
		}
		return result;
	}

	private LambdaQueryWrapper<Waybill> buildQuery(WaybillVO waybill) {
		TransportBusinessSupport.validateAllDept(waybill.getAllDept(), "运单管理");
		LambdaQueryWrapper<Waybill> queryWrapper = Wrappers.<Waybill>lambdaQuery().eq(Waybill::getIsDeleted, 0);
		if (!Objects.equals(waybill.getAllDept(), 1)) {
			queryWrapper.eq(Waybill::getDeptId, TransportBusinessSupport.currentDeptId("运单管理"));
		} else if (Func.isNotEmpty(waybill.getDeptId())) {
			queryWrapper.eq(Waybill::getDeptId, waybill.getDeptId());
		}
		if (Func.isNotEmpty(waybill.getWaybillNo())) {
			queryWrapper.eq(Waybill::getWaybillNo, waybill.getWaybillNo());
		}
		if (Func.isNotEmpty(waybill.getProjectName())) {
			queryWrapper.like(Waybill::getProjectName, waybill.getProjectName());
		}
		if (Func.isNotEmpty(waybill.getContractName())) {
			queryWrapper.like(Waybill::getContractName, waybill.getContractName());
		}
		if (Func.isNotEmpty(waybill.getCustomerName())) {
			queryWrapper.like(Waybill::getCustomerName, waybill.getCustomerName());
		}
		if (Func.isNotEmpty(waybill.getTransportType())) {
			queryWrapper.eq(Waybill::getTransportType, waybill.getTransportType());
		}
		if (Func.isNotEmpty(waybill.getCargoName())) {
			queryWrapper.like(Waybill::getCargoName, waybill.getCargoName());
		}
		if (Func.isNotEmpty(waybill.getCargoType())) {
			queryWrapper.eq(Waybill::getCargoType, waybill.getCargoType());
		}
		if (Func.isNotEmpty(waybill.getDepartureAddress())) {
			queryWrapper.like(Waybill::getDepartureAddress, waybill.getDepartureAddress());
		}
		if (Func.isNotEmpty(waybill.getArrivalAddress())) {
			queryWrapper.like(Waybill::getArrivalAddress, waybill.getArrivalAddress());
		}
		if (Func.isNotEmpty(waybill.getCarrierName())) {
			queryWrapper.like(Waybill::getCarrierName, waybill.getCarrierName());
		}
		if (Func.isNotEmpty(waybill.getDriverName())) {
			queryWrapper.like(Waybill::getDriverName, waybill.getDriverName());
		}
		if (Func.isNotEmpty(waybill.getVehicleNo())) {
			queryWrapper.like(Waybill::getVehicleNo, waybill.getVehicleNo());
		}
		if (Func.isNotEmpty(waybill.getOriginalNo())) {
			queryWrapper.like(Waybill::getOriginalNo, waybill.getOriginalNo());
		}
		if (Func.isNotEmpty(waybill.getBusinessStatus())) {
			queryWrapper.eq(Waybill::getBusinessStatus, waybill.getBusinessStatus());
		}
		if (Func.isNotEmpty(waybill.getDataSource())) {
			queryWrapper.eq(Waybill::getDataSource, waybill.getDataSource());
		}
		if (Func.isNotEmpty(waybill.getPlanName())) {
			queryWrapper.like(Waybill::getPlanName, waybill.getPlanName());
		}
		if (Func.isNotEmpty(waybill.getMasterNo())) {
			queryWrapper.like(Waybill::getMasterNo, waybill.getMasterNo());
		}
		if (Func.isNotEmpty(waybill.getLoadingNo())) {
			queryWrapper.like(Waybill::getLoadingNo, waybill.getLoadingNo());
		}
		if (Objects.equals(waybill.getOnlyUnassignedLoading(), 1)) {
			queryWrapper.and(wrapper -> wrapper.isNull(Waybill::getLoadingNo).or().eq(Waybill::getLoadingNo, ""));
		}
		if (Func.isNotEmpty(waybill.getBatchNo())) {
			queryWrapper.like(Waybill::getBatchNo, waybill.getBatchNo());
		}
		if (Func.isNotEmpty(waybill.getRelationNo())) {
			queryWrapper.like(Waybill::getRelationNo, waybill.getRelationNo());
		}
		if (Func.isNotEmpty(waybill.getCurrentProcessNode())) {
			queryWrapper.like(Waybill::getCurrentProcessNode, waybill.getCurrentProcessNode());
		}
		if (Func.isNotEmpty(waybill.getRemark())) {
			queryWrapper.like(Waybill::getRemark, waybill.getRemark());
		}
		queryWrapper.orderByDesc(Waybill::getCreateTime);
		return queryWrapper;
	}

	private void prepare(Waybill waybill) {
		waybill.setWaybillNo(TransportBusinessSupport.trimToNull(waybill.getWaybillNo()));
		waybill.setProjectName(TransportBusinessSupport.trimToNull(waybill.getProjectName()));
		waybill.setContractName(TransportBusinessSupport.trimToNull(waybill.getContractName()));
		waybill.setCustomerName(TransportBusinessSupport.trimToNull(waybill.getCustomerName()));
		waybill.setTransportType(TransportBusinessSupport.trimToNull(waybill.getTransportType()));
		waybill.setCargoName(TransportBusinessSupport.trimToNull(waybill.getCargoName()));
		waybill.setCargoType(TransportBusinessSupport.trimToNull(waybill.getCargoType()));
		waybill.setSpecification(TransportBusinessSupport.trimToNull(waybill.getSpecification()));
		waybill.setModel(TransportBusinessSupport.trimToNull(waybill.getModel()));
		waybill.setQuantityUnit(TransportBusinessSupport.trimToNull(waybill.getQuantityUnit()));
		waybill.setDepartureName(TransportBusinessSupport.trimToNull(waybill.getDepartureName()));
		waybill.setDepartureAddress(TransportBusinessSupport.trimToNull(waybill.getDepartureAddress()));
		waybill.setDepartureContact(TransportBusinessSupport.trimToNull(waybill.getDepartureContact()));
		waybill.setDeparturePhone(TransportBusinessSupport.trimToNull(waybill.getDeparturePhone()));
		waybill.setArrivalName(TransportBusinessSupport.trimToNull(waybill.getArrivalName()));
		waybill.setArrivalAddress(TransportBusinessSupport.trimToNull(waybill.getArrivalAddress()));
		waybill.setArrivalContact(TransportBusinessSupport.trimToNull(waybill.getArrivalContact()));
		waybill.setArrivalPhone(TransportBusinessSupport.trimToNull(waybill.getArrivalPhone()));
		waybill.setTaskEntryMode(TransportBusinessSupport.trimToNull(waybill.getTaskEntryMode()));
		waybill.setCarrierType(TransportBusinessSupport.trimToNull(waybill.getCarrierType()));
		waybill.setCarrierName(TransportBusinessSupport.trimToNull(waybill.getCarrierName()));
		waybill.setDriverName(TransportBusinessSupport.trimToNull(waybill.getDriverName()));
		waybill.setDriverPhone(TransportBusinessSupport.trimToNull(waybill.getDriverPhone()));
		waybill.setVehicleNo(TransportBusinessSupport.trimToNull(waybill.getVehicleNo()));
		waybill.setTrailerVehicleNo(TransportBusinessSupport.trimToNull(waybill.getTrailerVehicleNo()));
		waybill.setEscortName(TransportBusinessSupport.trimToNull(waybill.getEscortName()));
		waybill.setEscortPhone(TransportBusinessSupport.trimToNull(waybill.getEscortPhone()));
		waybill.setPriceUnit(TransportBusinessSupport.trimToNull(waybill.getPriceUnit()));
		waybill.setTaskRemark(TransportBusinessSupport.trimToNull(waybill.getTaskRemark()));
		waybill.setOriginalNo(TransportBusinessSupport.trimToNull(waybill.getOriginalNo()));
		waybill.setBusinessStatus(TransportBusinessSupport.trimToNull(waybill.getBusinessStatus()));
		waybill.setDataSource(TransportBusinessSupport.trimToNull(waybill.getDataSource()));
		waybill.setPlanName(TransportBusinessSupport.trimToNull(waybill.getPlanName()));
		waybill.setMasterNo(TransportBusinessSupport.trimToNull(waybill.getMasterNo()));
		waybill.setLoadingNo(TransportBusinessSupport.trimToNull(waybill.getLoadingNo()));
		waybill.setBatchNo(TransportBusinessSupport.trimToNull(waybill.getBatchNo()));
		waybill.setRelationNo(TransportBusinessSupport.trimToNull(waybill.getRelationNo()));
		waybill.setCurrentProcessNode(TransportBusinessSupport.trimToNull(waybill.getCurrentProcessNode()));
		waybill.setGoodsJson(TransportBusinessSupport.trimToNull(waybill.getGoodsJson()));
		waybill.setCarrierJson(TransportBusinessSupport.trimToNull(waybill.getCarrierJson()));
		waybill.setTaskInfoJson(TransportBusinessSupport.trimToNull(waybill.getTaskInfoJson()));
		waybill.setProcessJson(TransportBusinessSupport.trimToNull(waybill.getProcessJson()));
		waybill.setFreightJson(TransportBusinessSupport.trimToNull(waybill.getFreightJson()));
		waybill.setAttachmentsJson(TransportBusinessSupport.trimToNull(waybill.getAttachmentsJson()));
		waybill.setDeptName(TransportBusinessSupport.trimToNull(waybill.getDeptName()));
		waybill.setRemark(TransportBusinessSupport.trimToNull(waybill.getRemark()));
		fillJsonFromFlatFields(waybill);
		if (Func.isEmpty(waybill.getDeptId())) {
			Dept dept = TransportBusinessSupport.currentDept("运单管理");
			waybill.setDeptId(dept.getId());
			waybill.setDeptName(dept.getDeptName());
		}
		if (waybill.getStatus() == null) { waybill.setStatus(1); }
		if (Func.isEmpty(waybill.getBusinessStatus())) { waybill.setBusinessStatus("pending"); }
	}

	private void validate(Waybill waybill) {
		TransportBusinessSupport.validateRequired(waybill.getProjectName(), "项目不能为空");
		TransportBusinessSupport.validateRequired(waybill.getContractName(), "客户合同不能为空");
		TransportBusinessSupport.validateRequired(waybill.getTransportType(), "运输类型不能为空");
		TransportBusinessSupport.validateRequired(waybill.getCargoName(), "请输入货物名称");
		TransportBusinessSupport.validateRequired(waybill.getCargoType(), "货物类型不能为空");
		TransportBusinessSupport.validateRequired(waybill.getQuantityUnit(), "数量单位不能为空");
		TransportBusinessSupport.validateRequired(waybill.getCarrierType(), "承运类型不能为空");
		TransportBusinessSupport.validateRequired(waybill.getVehicleNo(), "车牌号不能为空");
		if ("承运商".equals(waybill.getCarrierType())) {
			TransportBusinessSupport.validateRequired(waybill.getCarrierName(), "承运商不能为空");
		} else {
			TransportBusinessSupport.validateRequired(waybill.getDriverName(), "司机不能为空");
			TransportBusinessSupport.validateRequired(waybill.getDriverPhone(), "司机手机号不能为空");
			TransportBusinessSupport.validateRequired(waybill.getTrailerVehicleNo(), "挂车车牌号不能为空");
			TransportBusinessSupport.validateRequired(waybill.getEscortName(), "押运人不能为空");
			TransportBusinessSupport.validateRequired(waybill.getEscortPhone(), "押运人手机号不能为空");
			if (Func.isEmpty(waybill.getMileage())) {
				throw new ServiceException("里程不能为空");
			}
		}
		TransportBusinessSupport.validateRequired(waybill.getCarrierJson(), "承运信息不能为空");
		if (Func.isEmpty(waybill.getQuantity())) {
			throw new ServiceException("数量不能为空");
		}
		TransportBusinessSupport.validateLength(waybill.getWaybillNo(), 255, "运单号不能超过255字");
		TransportBusinessSupport.validateLength(waybill.getProjectName(), 255, "项目不能超过255字");
		TransportBusinessSupport.validateLength(waybill.getContractName(), 255, "客户合同不能超过255字");
		TransportBusinessSupport.validateLength(waybill.getCustomerName(), 255, "客户名称不能超过255字");
		TransportBusinessSupport.validateLength(waybill.getTransportType(), 255, "运输类型不能超过255字");
		TransportBusinessSupport.validateLength(waybill.getCargoName(), 255, "货物名称不能超过255字");
		TransportBusinessSupport.validateLength(waybill.getCargoType(), 255, "货物类型不能超过255字");
		TransportBusinessSupport.validateLength(waybill.getSpecification(), 255, "规格不能超过255字");
		TransportBusinessSupport.validateLength(waybill.getModel(), 255, "型号不能超过255字");
		TransportBusinessSupport.validateLength(waybill.getQuantityUnit(), 50, "数量单位不能超过50字");
		TransportBusinessSupport.validateLength(waybill.getDepartureName(), 255, "发货地不能超过255字");
		TransportBusinessSupport.validateLength(waybill.getDepartureAddress(), 255, "发货地址不能超过255字");
		TransportBusinessSupport.validateLength(waybill.getDepartureContact(), 255, "发货联系人不能超过255字");
		TransportBusinessSupport.validateLength(waybill.getDeparturePhone(), 50, "发货联系方式不能超过50字");
		TransportBusinessSupport.validateLength(waybill.getArrivalName(), 255, "收货地不能超过255字");
		TransportBusinessSupport.validateLength(waybill.getArrivalAddress(), 255, "收货地址不能超过255字");
		TransportBusinessSupport.validateLength(waybill.getArrivalContact(), 255, "收货联系人不能超过255字");
		TransportBusinessSupport.validateLength(waybill.getArrivalPhone(), 50, "收货联系方式不能超过50字");
		TransportBusinessSupport.validateLength(waybill.getTaskEntryMode(), 50, "任务录入模式不能超过50字");
		TransportBusinessSupport.validateLength(waybill.getCarrierType(), 50, "承运类型不能超过50字");
		TransportBusinessSupport.validateLength(waybill.getCarrierName(), 255, "承运商名称不能超过255字");
		TransportBusinessSupport.validateLength(waybill.getDriverName(), 255, "司机姓名不能超过255字");
		TransportBusinessSupport.validateLength(waybill.getDriverPhone(), 50, "司机手机号不能超过50字");
		TransportBusinessSupport.validateLength(waybill.getVehicleNo(), 255, "车/船/航班/班列号不能超过255字");
		TransportBusinessSupport.validateLength(waybill.getTrailerVehicleNo(), 255, "挂车车牌号不能超过255字");
		TransportBusinessSupport.validateLength(waybill.getEscortName(), 255, "押运人不能超过255字");
		TransportBusinessSupport.validateLength(waybill.getEscortPhone(), 50, "押运人手机号不能超过50字");
		TransportBusinessSupport.validateLength(waybill.getPriceUnit(), 50, "计价单位不能超过50字");
		TransportBusinessSupport.validateLength(waybill.getTaskRemark(), 200, "任务备注不能超过200字");
		TransportBusinessSupport.validateLength(waybill.getOriginalNo(), 255, "原始单号不能超过255字");
		TransportBusinessSupport.validateLength(waybill.getBusinessStatus(), 255, "业务状态不能超过255字");
		TransportBusinessSupport.validateLength(waybill.getDataSource(), 255, "数据来源不能超过255字");
		TransportBusinessSupport.validateLength(waybill.getPlanName(), 255, "计划名称不能超过255字");
		TransportBusinessSupport.validateLength(waybill.getMasterNo(), 255, "多联总单不能超过255字");
		TransportBusinessSupport.validateLength(waybill.getLoadingNo(), 255, "配载单号不能超过255字");
		TransportBusinessSupport.validateLength(waybill.getBatchNo(), 255, "运单批次号不能超过255字");
		TransportBusinessSupport.validateLength(waybill.getRelationNo(), 255, "关联单号不能超过255字");
		TransportBusinessSupport.validateLength(waybill.getCurrentProcessNode(), 255, "当前过程节点不能超过255字");
		TransportBusinessSupport.validateLength(waybill.getGoodsJson(), 8000, "货物信息不能超过8000字");
		TransportBusinessSupport.validateLength(waybill.getCarrierJson(), 8000, "承运信息不能超过8000字");
		TransportBusinessSupport.validateLength(waybill.getTaskInfoJson(), 8000, "任务信息不能超过8000字");
		TransportBusinessSupport.validateLength(waybill.getProcessJson(), 8000, "过程节点不能超过8000字");
		TransportBusinessSupport.validateLength(waybill.getFreightJson(), 8000, "费用信息不能超过8000字");
		TransportBusinessSupport.validateLength(waybill.getAttachmentsJson(), 8000, "附件不能超过8000字");
		TransportBusinessSupport.validateLength(waybill.getDeptName(), 255, "所属组织不能超过255字");
		TransportBusinessSupport.validateLength(waybill.getRemark(), 200, "备注不能超过200字");
		TransportBusinessSupport.validatePhone(waybill.getDeparturePhone(), "发货联系方式格式不正确");
		TransportBusinessSupport.validatePhone(waybill.getArrivalPhone(), "收货联系方式格式不正确");
		TransportBusinessSupport.validatePhone(waybill.getDriverPhone(), "司机手机号格式不正确");
		TransportBusinessSupport.validatePhone(waybill.getEscortPhone(), "押运人手机号格式不正确");
		TransportBusinessSupport.validateNonNegative(waybill.getQuantity(), "数量");
		TransportBusinessSupport.validateNonNegative(waybill.getMileage(), "里程");
		TransportBusinessSupport.validateNonNegative(waybill.getUnitPrice(), "单价");
		TransportBusinessSupport.validateNonNegative(waybill.getOtherFeeTotal(), "其他费用合计");
		TransportBusinessSupport.validateDateRange(waybill.getStartDate(), waybill.getEndDate(), "开始日期不能晚于结束日期");
		if (waybill.getEstimatedStartTime() != null
			&& waybill.getEstimatedEndTime() != null
			&& waybill.getEstimatedEndTime().isBefore(waybill.getEstimatedStartTime())) {
			throw new ServiceException("预计完成日期不能早于预计发货日期");
		}
	}

	private Waybill loadEditable(Long id, boolean checkDept) {
		if (Func.isEmpty(id)) {
			throw new ServiceException("主键不能为空");
		}
		Waybill waybill = getById(id);
		if (Func.isEmpty(waybill) || Objects.equals(waybill.getIsDeleted(), 1)) {
			throw new ServiceException("运单管理不存在");
		}
		if (checkDept) {
			TransportBusinessSupport.assertCurrentDept(waybill.getDeptId(), "运单管理");
		}
		return waybill;
	}

	private boolean shouldSkipDelete(Waybill waybill) {
		return false;
	}

	private void fillJsonFromFlatFields(Waybill waybill) {
		if (Func.isEmpty(waybill.getTaskEntryMode())) {
			waybill.setTaskEntryMode("simple");
		}
		if (Func.isEmpty(waybill.getCarrierJson())) {
			waybill.setCarrierJson(buildImportCarrierJson(waybill));
		}
		if (Func.isEmpty(waybill.getTaskInfoJson())) {
			waybill.setTaskInfoJson(buildTaskInfoJson(waybill));
		}
		if (Func.isEmpty(waybill.getGoodsJson())) {
			waybill.setGoodsJson(buildGoodsJson(waybill));
		}
		if (Func.isEmpty(waybill.getFreightJson())) {
			waybill.setFreightJson(buildFreightJson(waybill));
		}
	}

	private String buildImportCarrierJson(Waybill waybill) {
		if (!hasAnyValue(
			waybill.getCarrierType(),
			waybill.getCarrierId(),
			waybill.getCarrierName(),
			waybill.getDriverId(),
			waybill.getDriverName(),
			waybill.getDriverPhone(),
			waybill.getVehicleNo(),
			waybill.getTrailerVehicleNo(),
			waybill.getEscortName(),
			waybill.getEscortPhone(),
			waybill.getMileage()
		)) {
			return null;
		}
		Map<String, Object> carrier = new LinkedHashMap<>();
		putIfNotEmpty(carrier, "carrierType", waybill.getCarrierType());
		putIfNotEmpty(carrier, "carrierId", waybill.getCarrierId());
		putIfNotEmpty(carrier, "carrier", waybill.getCarrierName());
		putIfNotEmpty(carrier, "carrierName", waybill.getCarrierName());
		putIfNotEmpty(carrier, "driverId", waybill.getDriverId());
		putIfNotEmpty(carrier, "driverName", waybill.getDriverName());
		putIfNotEmpty(carrier, "driverPhone", waybill.getDriverPhone());
		putIfNotEmpty(carrier, "vehicleNo", waybill.getVehicleNo());
		putIfNotEmpty(carrier, "trailerVehicleNo", waybill.getTrailerVehicleNo());
		putIfNotEmpty(carrier, "escortName", waybill.getEscortName());
		putIfNotEmpty(carrier, "escortPhone", waybill.getEscortPhone());
		putIfNotEmpty(carrier, "mileage", waybill.getMileage());
		putIfNotEmpty(carrier, "taskEntryMode", waybill.getTaskEntryMode());
		return JsonUtil.toJson(List.of(carrier));
	}

	private String buildTaskInfoJson(Waybill waybill) {
		if (!hasAnyValue(
			waybill.getTaskEntryMode(),
			waybill.getCarrierType(),
			waybill.getCarrierId(),
			waybill.getCarrierName(),
			waybill.getDriverId(),
			waybill.getDriverName(),
			waybill.getDriverPhone(),
			waybill.getVehicleNo(),
			waybill.getTrailerVehicleNo(),
			waybill.getEscortName(),
			waybill.getEscortPhone(),
			waybill.getMileage(),
			waybill.getEstimatedStartTime(),
			waybill.getEstimatedEndTime(),
			waybill.getUnitPrice(),
			waybill.getPriceUnit(),
			waybill.getOtherFeeTotal(),
			waybill.getTaskRemark()
		)) {
			return null;
		}
		Map<String, Object> taskInfo = new LinkedHashMap<>();
		putIfNotEmpty(taskInfo, "taskEntryMode", waybill.getTaskEntryMode());
		putIfNotEmpty(taskInfo, "carrierType", waybill.getCarrierType());
		putIfNotEmpty(taskInfo, "carrierId", waybill.getCarrierId());
		putIfNotEmpty(taskInfo, "carrierName", waybill.getCarrierName());
		putIfNotEmpty(taskInfo, "driverId", waybill.getDriverId());
		putIfNotEmpty(taskInfo, "driverName", waybill.getDriverName());
		putIfNotEmpty(taskInfo, "driverPhone", waybill.getDriverPhone());
		putIfNotEmpty(taskInfo, "vehicleNo", waybill.getVehicleNo());
		putIfNotEmpty(taskInfo, "trailerVehicleNo", waybill.getTrailerVehicleNo());
		putIfNotEmpty(taskInfo, "escortName", waybill.getEscortName());
		putIfNotEmpty(taskInfo, "escortPhone", waybill.getEscortPhone());
		putIfNotEmpty(taskInfo, "mileage", waybill.getMileage());
		putIfNotEmpty(taskInfo, "estimatedStartTime", waybill.getEstimatedStartTime());
		putIfNotEmpty(taskInfo, "estimatedEndTime", waybill.getEstimatedEndTime());
		putIfNotEmpty(taskInfo, "unitPrice", waybill.getUnitPrice());
		putIfNotEmpty(taskInfo, "priceUnit", waybill.getPriceUnit());
		putIfNotEmpty(taskInfo, "otherFeeTotal", waybill.getOtherFeeTotal());
		putIfNotEmpty(taskInfo, "taskRemark", waybill.getTaskRemark());
		return JsonUtil.toJson(taskInfo);
	}

	private String buildGoodsJson(Waybill waybill) {
		if (!hasAnyValue(
			waybill.getCargoName(),
			waybill.getCargoType(),
			waybill.getSpecification(),
			waybill.getModel(),
			waybill.getQuantity(),
			waybill.getQuantityUnit()
		)) {
			return null;
		}
		Map<String, Object> goods = new LinkedHashMap<>();
		putIfNotEmpty(goods, "cargoName", waybill.getCargoName());
		putIfNotEmpty(goods, "cargoType", waybill.getCargoType());
		putIfNotEmpty(goods, "specification", waybill.getSpecification());
		putIfNotEmpty(goods, "model", waybill.getModel());
		putIfNotEmpty(goods, "quantity", waybill.getQuantity());
		putIfNotEmpty(goods, "quantityUnit", waybill.getQuantityUnit());
		putIfNotEmpty(goods, "mileage", waybill.getMileage());
		putIfNotEmpty(goods, "unitPrice", waybill.getUnitPrice());
		putIfNotEmpty(goods, "priceUnit", waybill.getPriceUnit());
		putIfNotEmpty(goods, "otherFeeTotal", waybill.getOtherFeeTotal());
		putIfNotEmpty(goods, "remark", waybill.getTaskRemark());
		return JsonUtil.toJson(List.of(goods));
	}

	private String buildFreightJson(Waybill waybill) {
		if (!hasAnyValue(waybill.getUnitPrice(), waybill.getPriceUnit(), waybill.getOtherFeeTotal())) {
			return null;
		}
		Map<String, Object> freight = new LinkedHashMap<>();
		putIfNotEmpty(freight, "unitPrice", waybill.getUnitPrice());
		putIfNotEmpty(freight, "priceUnit", waybill.getPriceUnit());
		putIfNotEmpty(freight, "otherFeeTotal", waybill.getOtherFeeTotal());
		return JsonUtil.toJson(List.of(freight));
	}

	private boolean hasAnyValue(Object... values) {
		for (Object value : values) {
			if (Func.isNotEmpty(value)) {
				return true;
			}
		}
		return false;
	}

	private void putIfNotEmpty(Map<String, Object> map, String key, Object value) {
		if (Func.isNotEmpty(value)) {
			map.put(key, value);
		}
	}

	private synchronized String nextCode() {
		String prefix = "YD" + LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
		List<Waybill> latestList = list(Wrappers.<Waybill>lambdaQuery()
			.select(Waybill::getWaybillNo)
			.likeRight(Waybill::getWaybillNo, prefix)
			.orderByDesc(Waybill::getWaybillNo)
			.last("LIMIT 1"));
		int next = 1;
		if (Func.isNotEmpty(latestList) && Func.isNotEmpty(latestList.get(0).getWaybillNo())) {
			String serial = latestList.get(0).getWaybillNo().substring(prefix.length());
			if (serial.chars().allMatch(Character::isDigit)) {
				next = Integer.parseInt(serial) + 1;
			}
		}
		return prefix + String.format("%04d", next);
	}

}
