Commit c5885899 by chamberone

Merge branch 'develop' of https://zhangguoping@gitlab.dituhui.com/bsh/project/pr…

…oject.git into develop
2 parents 334fab9e e9d01980
Showing with 1098 additions and 53 deletions
package com.dituhui.pea.order.common;
import com.dituhui.pea.order.common.jackson.DateUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Date;
@Getter
@RequiredArgsConstructor
@AllArgsConstructor
public class DateSplit {
private Date startDateTime;
private Date endDateTime;
public void setStartDateTime(Date startDateTime) {
this.startDateTime = startDateTime;
}
public void setEndDateTime(Date endDateTime) {
this.endDateTime = endDateTime;
}
public LocalDateTime getStartLocalDateTime() {
return DateUtil.fromDate(this.startDateTime);
}
public LocalDateTime getLocalEndDateTime() {
return DateUtil.fromDate(this.startDateTime);
}
/**
* 获取开始时间片
*
* @return 开始时间
*/
public LocalTime getStartTime() {
return this.getStartLocalDateTime().toLocalTime();
}
/**
* 获取结束时间片
*
* @return 结束时间
*/
public LocalTime getEndTime() {
return this.getLocalEndDateTime().toLocalTime();
}
}
\ No newline at end of file
......@@ -4,11 +4,14 @@ import lombok.experimental.UtilityClass;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
......@@ -29,7 +32,7 @@ public class DateTimeUtil {
/**
* 时间格式 HH:mm:ss
*/
public static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern(DateUtil.PATTERN_TIME);
public static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern(DateUtil.PATTERN_TIME);
/**
* 日期时间格式化
......@@ -161,4 +164,22 @@ public class DateTimeUtil {
return result;
}
/**
* 当前时间是否在时间指定范围内, 包含起始时间<br>
*
* @param time 被检查的时间
* @param beginTime 起始时间
* @param endTime 结束时间
* @return 是否在范围内
* @since 3.0.8
*/
public static boolean isIn(LocalTime time, LocalTime beginTime, LocalTime endTime) {
if ((Objects.equals(beginTime, time) || beginTime.isBefore(time))
&& (endTime.isAfter(time) || Objects.equals(endTime, time))) {
return true;
}
return false;
}
}
package com.dituhui.pea.order.common.jackson;
import com.dituhui.pea.order.common.DateSplit;
import lombok.experimental.UtilityClass;
import org.springframework.util.Assert;
......@@ -8,6 +9,7 @@ import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Period;
import java.time.ZoneId;
import java.time.ZonedDateTime;
......@@ -15,9 +17,12 @@ import java.time.format.DateTimeFormatter;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAmount;
import java.time.temporal.TemporalQuery;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.TimeZone;
/**
......@@ -495,4 +500,95 @@ public class DateUtil {
}
//CHECKSTYLE:ON
/**
* 按照分钟切割时间区间
*/
public static List<DateSplit> splitByMinute(Date startTime, Date endTime, int intervalMinutes) {
if (endTime.getTime() <= startTime.getTime()) {
return null;
}
List<DateSplit> dateSplits = new ArrayList<>(50);
DateSplit param = new DateSplit();
param.setStartDateTime(startTime);
param.setEndDateTime(endTime);
param.setEndDateTime(addMinute(startTime, intervalMinutes));
while (true) {
param.setStartDateTime(startTime);
Date tempEndTime = addMinute(startTime, intervalMinutes);
if (tempEndTime.getTime() >= endTime.getTime()) {
tempEndTime = endTime;
}
param.setEndDateTime(tempEndTime);
dateSplits.add(new DateSplit(param.getStartDateTime(), param.getEndDateTime()));
startTime = addMinute(startTime, intervalMinutes);
if (startTime.getTime() >= endTime.getTime()) {
break;
}
if (param.getEndDateTime().getTime() >= endTime.getTime()) {
break;
}
}
return dateSplits;
}
/**
* 按照小时切割时间区间
*
* @param startTime 开始时间
* @param endTime 结束时间
* @param intervalHours 时间片大小 (小时)
* @param splitAdjacency 相邻时间片间隔(小时),此值必须小于等于 'intervalHours' 值且大于等于0
* @return 返回对应的时间片
*/
public static List<DateSplit> splitByHour(Date startTime, Date endTime, int intervalHours, int splitAdjacency) {
if (endTime.getTime() <= startTime.getTime()) {
return Collections.emptyList();
}
List<DateSplit> dateSplits = new ArrayList<>(60);
DateSplit param = new DateSplit();
param.setStartDateTime(startTime);
param.setEndDateTime(endTime);
param.setEndDateTime(addHours(startTime, intervalHours));
while (true) {
param.setStartDateTime(startTime);
Date tempEndTime = addHours(startTime, intervalHours);
if (tempEndTime.getTime() >= endTime.getTime()) {
tempEndTime = endTime;
}
param.setEndDateTime(tempEndTime);
dateSplits.add(new DateSplit(param.getStartDateTime(), param.getEndDateTime()));
startTime = addHours(startTime, splitAdjacency);
if (startTime.getTime() >= endTime.getTime()) {
break;
}
if (param.getEndDateTime().getTime() >= endTime.getTime()) {
break;
}
}
return dateSplits;
}
private static Date addMinute(Date date, int minute) {
return add(date, Calendar.MINUTE, minute);
}
private static Date addHours(Date date, int hours) {
return add(date, Calendar.HOUR_OF_DAY, hours);
}
private static Date add(final Date date, final int calendarField, final int amount) {
final Calendar c = Calendar.getInstance();
c.setTime(date);
c.add(calendarField, amount);
return c.getTime();
}
}
......@@ -3,23 +3,45 @@ package com.dituhui.pea.order.controller;
import com.dituhui.pea.common.Result;
import com.dituhui.pea.order.common.jackson.DateUtil;
import com.dituhui.pea.order.common.jackson.JsonUtil;
import com.dituhui.pea.order.dto.param.*;
import com.dituhui.pea.order.dao.TableCodeCheckDao;
import com.dituhui.pea.order.dto.param.BaseDistance;
import com.dituhui.pea.order.dto.param.BaseDistanceParam;
import com.dituhui.pea.order.dto.param.CapacityQueryDTO;
import com.dituhui.pea.order.dto.param.EngineerCalendarResultDTO;
import com.dituhui.pea.order.dto.param.EngineerOrderParam;
import com.dituhui.pea.order.dto.param.EstimateDTO;
import com.dituhui.pea.order.dto.param.Order;
import com.dituhui.pea.order.dto.param.OrderConfirmParam;
import com.dituhui.pea.order.dto.param.OrderConfirmResult;
import com.dituhui.pea.order.dto.param.OrderDTO;
import com.dituhui.pea.order.entity.TypeCodeCheckTableEntity;
import com.dituhui.pea.order.service.CapacityQueryService;
import com.dituhui.pea.order.service.EngineerCalendarService;
import com.dituhui.pea.order.service.OrderCreateService;
import com.dituhui.pea.order.service.PeaOuterAPIService;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.IOException;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;
/**
* PEA 对外接口
......@@ -32,8 +54,9 @@ public class PeaApiController {
private final EngineerCalendarService engineerCalendarService;
private final PeaOuterAPIService peaOuterAPIService;
@Autowired
private OrderCreateService orderCreateService;
private final CapacityQueryService capacityQueryService;
private final OrderCreateService orderCreateService;
private final TableCodeCheckDao tableCodeCheckDao;
private static final String capacity = "{\"groupId\": \"G100038\", \"params\": {\"beginDate\": \"2023-07-21\", \"endDate\": \"2023-07-27\", \"services\": [{\"brand\": \"博世\", \"productType\": \"多门冰箱\", \"serviceType\": \"商场样机安装\"}], \"location\": {\"addressId\":\"21231231\", \"latitude\": 120.608463, \"longitude\": 31.318442, \"name\": \"江苏省苏州市姑苏区蒋庙前\", \"address\": \"江苏省苏州市姑苏区解放大街123号\"}}, \"takeTime\": 720, \"datas\": [{\"date\": \"2023-07-21\", \"segments\": [{\"maxDuration\": 90, \"name\": \"全天\", \"beginTime\": \"2023-07-21 08:00:00\", \"endTime\": \"2023-07-21 17:59:59\", \"status\": 1, \"remain\": 3500}, {\"maxDuration\": 45, \"name\": \"上午\", \"beginTime\": \"2023-07-21 08:00:00\", \"endTime\": \"2023-07-21 11:59:59\", \"status\": 1, \"remain\": 2500}, {\"maxDuration\": 60, \"name\": \"下午\", \"beginTime\": \"2023-07-21 13:00:00\", \"endTime\": \"2023-07-21 17:59:59\", \"status\": 0, \"remain\": 1000}, {\"maxDuration\": 60, \"name\": \"时间段\", \"beginTime\": \"2023-07-21 13:00:00\", \"endTime\": \"2023-07-21 15:00:00\", \"status\": 1, \"remain\": 480}]}]}";
......@@ -83,23 +106,34 @@ public class PeaApiController {
* @return 容量列表
*/
@PostMapping("/capacity/query")
public Result<CapacityQueryDTO.Result> capacityQuery(@Validated @RequestBody CapacityQueryDTO.Request reqDTO) throws IOException {
public Result<CapacityQueryDTO.Result> capacityQuery(@Validated @RequestBody CapacityQueryDTO.Request reqDTO) {
//查询日期起止参数限制为一月
ArrayList<String> strings = new ArrayList<>();
strings.add(half_capacity);
strings.add(day_capacity);
strings.add(half_capacity2);
strings.add(date_capacity);
Random random = new Random();
int i = random.nextInt(4);
CapacityQueryDTO.Result result = JsonUtil.parse(strings.get(i), CapacityQueryDTO.Result.class).get();
result.setParams(reqDTO);
return Result.success(result);
LocalDate startDate = reqDTO.getBeginDate().toInstant().atZone(ZoneId.of("+8")).toLocalDate();
LocalDate endDate = reqDTO.getEndDate().toInstant().atZone(ZoneId.of("+8")).toLocalDate();
List<CapacityQueryDTO.Service> services = reqDTO.getServices();
List<CapacityQueryDTO.Service> convertService = services.stream()
.map(source -> {
TypeCodeCheckTableEntity brand = tableCodeCheckDao.findByTypeAndCode("BRAND", source.getBrand());
TypeCodeCheckTableEntity type = tableCodeCheckDao.findByTypeAndCode("TYPE", source.getProductType());
TypeCodeCheckTableEntity skill = tableCodeCheckDao.findByTypeAndCode("SKILL", source.getServiceType());
CapacityQueryDTO.Service service = new CapacityQueryDTO.Service();
service.setBrand(fixBrand(brand.getName()));
service.setProductType(type.getName());
service.setServiceType(skill.getName());
return service;
}).collect(Collectors.toList());
return capacityQueryService.matchCapacityData(convertService, reqDTO.getLocation(), startDate, endDate);
}
private String fixBrand(String brand) {
if (!brand.equals("嘉格纳")) {
return "博世/西门子以及其他品牌";
} else {
return brand;
}
}
/**
* 4.2 改派到人容量看板查询
......@@ -117,7 +151,7 @@ public class PeaApiController {
@RequestParam("beginDate") Date beginDate,
@DateTimeFormat(pattern = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
Date endDate) throws IOException {
Date endDate) throws IOException {
ArrayList<String> strings = new ArrayList<>();
strings.add(half_capacity);
......
......@@ -12,6 +12,7 @@ import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Set;
@Repository
public interface EngineerInfoDao extends JpaRepository<EngineerInfoEntity, Integer>, JpaSpecificationExecutor<EngineerInfoEntity> {
......@@ -37,13 +38,48 @@ public interface EngineerInfoDao extends JpaRepository<EngineerInfoEntity, Integ
return (root, query, criteriaBuilder) -> root.get("engineerCode").in(engineerCodes);
}
/**
* 根据技能、产品、产品类型 分站ID获取分站下用户对应技能的技术员
*
* @param brand 产品
* @param productType 产品类型
* @param skill 技能
* @param groupId 分站ID
* @return 工程师信息
*/
@Query(value = "SELECT ei.* FROM skill_info si LEFT JOIN skill_group sg ON sg.skill_group_code =si.skill_group_code "
+ " LEFT JOIN engineer_skill_group esg ON sg.skill_group_code = esg.skill_group_code "
+ " LEFT JOIN engineer_info ei ON esg.engineer_code = ei.engineer_code "
+ " WHERE si.brand = :brand AND si.type= :productType AND si.skill = :skill "
+ " AND group_id = :groupId ", nativeQuery = true)
Set<EngineerInfoEntity> listByBrandAndTypeAndSkillAndGroupId(@Param("brand") String brand, @Param("productType") String productType,
@Param("skill") String skill, @Param("groupId") String groupId);
/**
* 根据技能、产品、产品类型 工作队获取分站下用户对应技能的技术员
*
* @param brand 产品
* @param productType 产品类型
* @param skill 技能
* @param teamId 工作队ID
* @return 工程师信息
*/
@Query(value = "SELECT ei.* FROM skill_info si LEFT JOIN skill_group sg ON sg.skill_group_code =si.skill_group_code \n" +
" LEFT JOIN engineer_skill_group esg ON sg.skill_group_code = esg.skill_group_code \n" +
" LEFT JOIN engineer_info ei ON esg.engineer_code = ei.engineer_code\n" +
" LEFT JOIN org_team_engineer ote ON ote.engineer_code = ei.engineer_code \n" +
" WHERE si.brand = :brand AND si.type= :productType AND si.skill = :skill \n" +
" AND ote.team_id = :teamId ", nativeQuery = true)
Set<EngineerInfoEntity> listByBrandAndTypeAndSkillAndTeamId(@Param("brand") String brand, @Param("productType") String productType,
@Param("skill") String skill, @Param("teamId") String teamId);
@Query(value = "SELECT ei.* from engineer_info ei left join engineer_skill_group esg on ei.engineer_code = esg.engineer_code " +
"left join skill_info si on si.skill_group_code = esg.skill_group_code left join map_layer_customize mlc on mlc.layer_id = si.layer_id " +
" WHERE si.brand = :brand and si.`type` = :productType and si.skill_code = :skillCode and ei.group_id in ( :teamIds ) " +
" order by mlc.priority ", nativeQuery = true)
List<EngineerInfoEntity> listBrandAndSkillCodeAndTeamIdIn(@Param("brand") String brand, @Param("productType") String productType,
@Param("skillCode") String skillCode, @Param("teamIds") List<String> teamIds);
@Param("skillCode") String skillCode, @Param("teamIds") List<String> teamIds);
@Query(value = "SELECT ei.* from engineer_info ei left join engineer_skill_group esg on ei.engineer_code = esg.engineer_code " +
......@@ -51,7 +87,7 @@ public interface EngineerInfoDao extends JpaRepository<EngineerInfoEntity, Integ
" WHERE si.brand = :brand and si.`type` = :productType and si.skill = :skill and ei.group_id in ( :teamIds ) " +
" order by mlc.priority ", nativeQuery = true)
List<EngineerInfoEntity> listBrandAndSkillAndTeamIdIn(@Param("brand") String brand, @Param("productType") String productType,
@Param("skill") String skill, @Param("teamIds") List<String> teamIds);
@Param("skill") String skill, @Param("teamIds") List<String> teamIds);
@Query(value = "SELECT ei.* from engineer_info ei left join engineer_skill_group esg on ei.engineer_code = esg.engineer_code " +
......@@ -59,5 +95,5 @@ public interface EngineerInfoDao extends JpaRepository<EngineerInfoEntity, Integ
" WHERE si.brand = :brand and si.`type` = :productType and si.skill = :skill and ei.engineer_code in ( :engineerCodes ) " +
" order by mlc.priority ", nativeQuery = true)
List<EngineerInfoEntity> listBrandAndSkillAndEngineerCodes(@Param("brand") String brand, @Param("productType") String productType,
@Param("skill") String skill, @Param("engineerCodes") List<String> engineerCodes);
@Param("skill") String skill, @Param("engineerCodes") List<String> engineerCodes);
}
package com.dituhui.pea.order.dao;
import com.dituhui.pea.order.entity.CapacityEngineerSliceUsedEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface EngineerSliceUsedCapacityDao extends JpaRepository<CapacityEngineerSliceUsedEntity, Long> {
List<CapacityEngineerSliceUsedEntity> findByWorkdayAndEngineerCode(String workDay, String engineerCode);
}
package com.dituhui.pea.order.dao;
import com.dituhui.pea.order.entity.TypeCodeCheckTableEntity;
import org.springframework.data.jpa.repository.JpaRepository;
public interface TableCodeCheckDao extends JpaRepository<TypeCodeCheckTableEntity, Integer> {
TypeCodeCheckTableEntity findByTypeAndCode(String type, String code);
}
package com.dituhui.pea.order.dao;
import com.dituhui.pea.order.entity.TimeSliceEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface TimeSliceDao extends JpaRepository<TimeSliceEntity, Long> {
List<TimeSliceEntity> findByType(String type);
}
......@@ -9,7 +9,6 @@ import org.springframework.validation.annotation.Validated;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
......@@ -136,7 +135,7 @@ public class CapacityQueryDTO {
/**
* 最大可用时长, 单位: 分钟
*/
private int maxDuration;
private long maxDuration;
/**
* 容量名称 全天/上午/下午/时间段
......@@ -165,7 +164,7 @@ public class CapacityQueryDTO {
/**
* 剩余容量
*/
private int remain;
private long remain;
}
......
......@@ -26,4 +26,13 @@ public class OrderConfirmParam extends OrderConfirmBaseParam{
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date appointmentTime;
/**
* 预约加单状态, 预约失败时传递
*
* CONFIRMED 预约成功
* REFUED 客户拒绝
* CANNOT_BE_REACHED 客户联系不上
*/
private String reasonForFailure;
}
package com.dituhui.pea.order.entity;
import lombok.Getter;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import java.time.LocalDateTime;
import java.util.Objects;
@Getter
@Entity
@Table(name = "capacity_engineer_slice_used")
public class CapacityEngineerSliceUsedEntity {
@Id
@Column(name = "id")
@GeneratedValue(generator = "uuid")
@GenericGenerator(name = "uuid", strategy = "uuid")
private String id;
@OneToOne(targetEntity = TimeSliceEntity.class,
cascade = {CascadeType.DETACH},
fetch = FetchType.EAGER)
@JoinColumn(name = "timme_slice", referencedColumnName = "id")
private TimeSliceEntity timmeSlice;
@Basic
@Column(name = "workday")
private String workday;
@Basic
@Column(name = "engineer_code")
private String engineerCode;
@Basic
@Column(name = "cap_total")
private Long capTotal;
@Basic
@Column(name = "cap_used")
private Long capUsed;
@Basic
@Column(name = "cap_used_travel")
private Long capUsedTravel;
@Basic
@Column(name = "cap_left")
private Long capLeft;
@Basic
@Column(name = "order_count")
private Long orderCount;
@Basic
@Column(name = "max_duration")
private Long maxDuration;
@Basic
@Column(name = "max_duration_type")
private String maxDurationType;
@Basic
@Column(name = "memo")
private String memo;
@Basic
@Column(name = "create_time")
private LocalDateTime createTime;
@Basic
@Column(name = "update_time")
private LocalDateTime updateTime;
public void setId(String id) {
this.id = id;
}
public void setTimmeSlice(TimeSliceEntity timmeSlice) {
this.timmeSlice = timmeSlice;
}
public void setWorkday(String workday) {
this.workday = workday;
}
public void setEngineerCode(String engineerCode) {
this.engineerCode = engineerCode;
}
public void setCapTotal(Long capTotal) {
this.capTotal = capTotal;
}
public void setCapUsed(Long capUsed) {
this.capUsed = capUsed;
}
public void setCapUsedTravel(Long capUsedTravel) {
this.capUsedTravel = capUsedTravel;
}
public void setCapLeft(Long capLeft) {
this.capLeft = capLeft;
}
public void setOrderCount(Long orderCount) {
this.orderCount = orderCount;
}
public void setMaxDuration(Long maxDuration) {
this.maxDuration = maxDuration;
}
public void setMaxDurationType(String maxDurationType) {
this.maxDurationType = maxDurationType;
}
public void setMemo(String memo) {
this.memo = memo;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CapacityEngineerSliceUsedEntity that = (CapacityEngineerSliceUsedEntity) o;
return id == that.id && timmeSlice == that.timmeSlice && Objects.equals(workday, that.workday) && Objects.equals(engineerCode, that.engineerCode) && Objects.equals(capTotal, that.capTotal) && Objects.equals(capUsed, that.capUsed) && Objects.equals(capUsedTravel, that.capUsedTravel) && Objects.equals(capLeft, that.capLeft) && Objects.equals(orderCount, that.orderCount) && Objects.equals(maxDuration, that.maxDuration) && Objects.equals(maxDurationType, that.maxDurationType) && Objects.equals(memo, that.memo) && Objects.equals(createTime, that.createTime) && Objects.equals(updateTime, that.updateTime);
}
@Override
public int hashCode() {
return Objects.hash(id, timmeSlice, workday, engineerCode, capTotal, capUsed, capUsedTravel, capLeft, orderCount, maxDuration, maxDurationType, memo, createTime, updateTime);
}
}
package com.dituhui.pea.order.entity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.Date;
@Data
@Entity
@Table(name="engineer_info")
@Table(name = "engineer_info")
@EqualsAndHashCode
public class EngineerInfoEntity {
/**
* 工程师id
......@@ -112,5 +113,6 @@ public class EngineerInfoEntity {
*/
private LocalDateTime updateTime = LocalDateTime.now();
public EngineerInfoEntity() {}
public EngineerInfoEntity() {
}
}
......@@ -226,4 +226,12 @@ public class OrderInfoEntity {
*/
@Column(name = "is_special_time")
private Integer isSpecialTime = 0;
/**
* 申请加单状态,CONFIRMED 预约成功
* REFUSED 客户拒绝
* CANNOT_BE_REACHED 客户联系不上
*/
@Column(name = "reason_for_failure")
private String reasonForFailure;
}
package com.dituhui.pea.order.entity;
import lombok.Getter;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.time.LocalDateTime;
import java.util.Objects;
@Getter
@Entity
@Table(name = "time_slice")
public class TimeSliceEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
@Column(name = "id")
private Long id;
@Basic
@Column(name = "type")
private String type;
@Basic
@Column(name = "name")
private String name;
@Basic
@Column(name = "start")
private String start;
@Basic
@Column(name = "end")
private String end;
@Basic
@Column(name = "create_time")
private LocalDateTime createTime;
@Basic
@Column(name = "update_time")
private LocalDateTime updateTime;
public void setId(Long id) {
this.id = id;
}
public void setType(String type) {
this.type = type;
}
public void setName(String name) {
this.name = name;
}
public void setStart(String start) {
this.start = start;
}
public void setEnd(String end) {
this.end = end;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TimeSliceEntity that = (TimeSliceEntity) o;
return id == that.id && Objects.equals(type, that.type) && Objects.equals(name, that.name) && Objects.equals(start, that.start) && Objects.equals(end, that.end) && Objects.equals(createTime, that.createTime) && Objects.equals(updateTime, that.updateTime);
}
@Override
public int hashCode() {
return Objects.hash(id, type, name, start, end, createTime, updateTime);
}
}
package com.dituhui.pea.order.entity;
import lombok.Getter;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.sql.Timestamp;
import java.util.Objects;
@Getter
@Entity
@Table(name = "type_code_check_table")
public class TypeCodeCheckTableEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
@Column(name = "id")
private Integer id;
@Basic
@Column(name = "type")
private String type;
@Basic
@Column(name = "name")
private String name;
@Basic
@Column(name = "code")
private String code;
@Basic
@Column(name = "create_time")
private Timestamp createTime;
@Basic
@Column(name = "update_time")
private Timestamp updateTime;
public void setId(int id) {
this.id = id;
}
public void setType(String type) {
this.type = type;
}
public void setName(String name) {
this.name = name;
}
public void setCode(String code) {
this.code = code;
}
public void setCreateTime(Timestamp createTime) {
this.createTime = createTime;
}
public void setUpdateTime(Timestamp updateTime) {
this.updateTime = updateTime;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TypeCodeCheckTableEntity that = (TypeCodeCheckTableEntity) o;
return id == that.id && Objects.equals(type, that.type) && Objects.equals(name, that.name) && Objects.equals(code, that.code) && Objects.equals(createTime, that.createTime) && Objects.equals(updateTime, that.updateTime);
}
@Override
public int hashCode() {
return Objects.hash(id, type, name, code, createTime, updateTime);
}
}
package com.dituhui.pea.order.scheduler;
import com.dituhui.pea.order.common.DateUtils;
import com.dituhui.pea.order.dao.*;
import com.dituhui.pea.order.entity.*;
import com.dituhui.pea.order.common.jackson.DateTimeUtil;
import com.dituhui.pea.order.common.jackson.DateUtil;
import com.dituhui.pea.order.dao.CapacityEngineerCalendarDao;
import com.dituhui.pea.order.dao.CapacityEngineerStatDao;
import com.dituhui.pea.order.dao.EngineerBusinessDao;
import com.dituhui.pea.order.dao.EngineerInfoDao;
import com.dituhui.pea.order.dao.EngineerSliceUsedCapacityDao;
import com.dituhui.pea.order.dao.OrderInfoDao;
import com.dituhui.pea.order.dao.TimeSliceDao;
import com.dituhui.pea.order.entity.CapacityEngineerCalendarEntity;
import com.dituhui.pea.order.entity.CapacityEngineerSliceUsedEntity;
import com.dituhui.pea.order.entity.CapacityEngineerStatEntity;
import com.dituhui.pea.order.entity.EngineerBusinessEntity;
import com.dituhui.pea.order.entity.EngineerInfoEntity;
import com.dituhui.pea.order.entity.OrderInfoEntity;
import com.dituhui.pea.order.entity.TimeSliceEntity;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
......@@ -14,7 +28,13 @@ import org.springframework.stereotype.Component;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Slf4j
......@@ -36,6 +56,10 @@ public class CalcEngineerCapacityScheduler {
private EngineerBusinessDao engineerBusinessDao;
@Autowired
private EngineerInfoDao engineerInfoDao;
@Autowired
private TimeSliceDao timeSliceDao;
@Autowired
private EngineerSliceUsedCapacityDao engineerSliceUsedCapacityDao;
@Scheduled(cron = "${scheduler.calc-engineer-capacity.cron-expr}")
public void run() {
......@@ -58,8 +82,20 @@ public class CalcEngineerCapacityScheduler {
}
private void calcOneEngineer(String date, String engineerCode) {
CapacityEngineerStatEntity statEntity = capacityEngineerStatDao.getByWorkdayAndEngineerCode(date, engineerCode);
if (statEntity == null) {
log.error("错误:日期[{}]技术员[{}]容量尚未初始化,忽略退出!", date, engineerCode);
return;
}
Set<String> ss = Set.of("CANCELED", "RESCHEDULED");
List<OrderInfoEntity> orders = orderInfoDao.findByDtAndEngineerCode(DateUtils.localDateFromStr(date), engineerCode);
// 根据capacity_engineer_calendar和order_info,来确定当天剩下的最大连续时间区块;
EngineerBusinessEntity businessEntity = engineerBusinessDao.getByEngineerCode(engineerCode);
// 就算单小时时间段工程师技能已用容量存储至时间切片工程师时间表内
extracted(date, engineerCode, orders, ss);
int used = orders.stream().map(e -> {
if (ss.contains(e.getOrderStatus())) {
return 0;
......@@ -70,13 +106,8 @@ public class CalcEngineerCapacityScheduler {
long cnt = orders.stream()
.filter(e -> !ss.contains(e.getOrderStatus()))
.count();
long max = getMaxRemainBlock(date, engineerCode, orders);
long max = getMaxRemainBlock(date, engineerCode, orders, businessEntity);
log.info("正在处理: 日期[{}]技术员[{}]容量相关信息 ==> used:{}, orderCnt:{}, maxDuration:{}", date, engineerCode, used, cnt, max);
CapacityEngineerStatEntity statEntity = capacityEngineerStatDao.getByWorkdayAndEngineerCode(date, engineerCode);
if (statEntity == null) {
log.error("错误:日期[{}]技术员[{}]容量尚未初始化,忽略退出!", date, engineerCode);
return;
}
statEntity.setOrderCount((int) cnt);
statEntity.setCapUsed(used);
statEntity.setCapLeft(statEntity.getCapTotal() - used);
......@@ -85,9 +116,47 @@ public class CalcEngineerCapacityScheduler {
capacityEngineerStatDao.save(statEntity);
}
private long getMaxRemainBlock(String date, String engineerCode, List<OrderInfoEntity> orders) {
private void extracted(String date, String engineerCode, List<OrderInfoEntity> orders, Set<String> ss) {
//查询时间片容量
List<CapacityEngineerSliceUsedEntity> engineerTimeSlice = engineerSliceUsedCapacityDao.findByWorkdayAndEngineerCode(date, engineerCode);
for (CapacityEngineerSliceUsedEntity sliceCap : engineerTimeSlice) {
final TimeSliceEntity timeSlice = sliceCap.getTimmeSlice();
LocalTime sliceStartLocalTime = LocalTime.parse(timeSlice.getStart(), DateUtil.TIME_FORMATTER);
LocalTime sliceEndLocalTime = LocalTime.parse(timeSlice.getEnd(), DateUtil.TIME_FORMATTER);
long lengthOfLeave = sliceCap.getCapTotal();
for (OrderInfoEntity order : orders) {
LocalTime planStartTime = order.getPlanStartTime().toLocalTime();
LocalTime planEndTime = order.getPlanEndTime().toLocalTime();
boolean startIn = DateTimeUtil.isIn(planStartTime, sliceStartLocalTime, sliceEndLocalTime);
boolean endAfter = planEndTime.isAfter(sliceEndLocalTime);
//8:00- 8:30 8:00 - 10:30
boolean contains = ss.contains(order.getOrderStatus());
//请假时间仅落在当前时间段内, 当前时间段请假时长为 请假结束时间- 请假开始时间
if (startIn && DateTimeUtil.isIn(planEndTime, sliceStartLocalTime, sliceEndLocalTime)) {
lengthOfLeave = contains
? lengthOfLeave - DateTimeUtil.betweenTwoTime(planStartTime, planEndTime, TimeUnit.MINUTES) :
lengthOfLeave + DateTimeUtil.betweenTwoTime(planStartTime, planEndTime, TimeUnit.MINUTES);
} else if (startIn && endAfter) {
//落在当前时间段和下一个时间段
lengthOfLeave = contains
? lengthOfLeave - DateTimeUtil.betweenTwoTime(planStartTime, sliceEndLocalTime, TimeUnit.MINUTES)
: lengthOfLeave +DateTimeUtil.betweenTwoTime(planStartTime, sliceEndLocalTime, TimeUnit.MINUTES);
} else if (planStartTime.isBefore(sliceStartLocalTime) && endAfter) {
lengthOfLeave = contains
? lengthOfLeave - DateTimeUtil.betweenTwoTime(sliceStartLocalTime, sliceEndLocalTime, TimeUnit.MINUTES):
lengthOfLeave + DateTimeUtil.betweenTwoTime(sliceStartLocalTime, sliceEndLocalTime, TimeUnit.MINUTES);
}
}
sliceCap.setCapLeft(lengthOfLeave);
}
engineerSliceUsedCapacityDao.saveAll(engineerTimeSlice);
}
private long getMaxRemainBlock(String date, String engineerCode, List<OrderInfoEntity> orders, EngineerBusinessEntity businessEntity) {
// 根据capacity_engineer_calendar和order_info,来确定当天剩下的最大连续时间区块;
EngineerBusinessEntity businessEntity = engineerBusinessDao.getByEngineerCode(engineerCode);
LocalDateTime startTime = DateUtils.localDateTimeFromStr(String.format("%s %s:00", date, businessEntity.getWorkOn()));
LocalDateTime endTime = DateUtils.localDateTimeFromStr(String.format("%s %s:00", date, businessEntity.getWorkOff()));
......
package com.dituhui.pea.order.scheduler;
import cn.hutool.core.collection.CollectionUtil;
import com.dituhui.pea.order.common.DateUtils;
import com.dituhui.pea.order.common.jackson.DateTimeUtil;
import com.dituhui.pea.order.common.jackson.DateUtil;
import com.dituhui.pea.order.dao.CapacityEngineerCalendarDao;
import com.dituhui.pea.order.dao.CapacityEngineerStatDao;
import com.dituhui.pea.order.dao.EngineerBusinessDao;
import com.dituhui.pea.order.dao.EngineerInfoDao;
import com.dituhui.pea.order.dao.EngineerSliceUsedCapacityDao;
import com.dituhui.pea.order.dao.TimeSliceDao;
import com.dituhui.pea.order.entity.CapacityEngineerCalendarEntity;
import com.dituhui.pea.order.entity.CapacityEngineerSliceUsedEntity;
import com.dituhui.pea.order.entity.CapacityEngineerStatEntity;
import com.dituhui.pea.order.entity.EngineerBusinessEntity;
import com.dituhui.pea.order.entity.EngineerInfoEntity;
import com.dituhui.pea.order.entity.TimeSliceEntity;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
......@@ -20,8 +27,11 @@ import org.springframework.stereotype.Component;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Slf4j
......@@ -45,6 +55,10 @@ public class InitEngineerCapacityScheduler {
private CapacityEngineerStatDao capacityEngineerStatDao;
@Autowired
private EngineerInfoDao engineerInfoDao;
@Autowired
private TimeSliceDao timeSliceDao;
@Autowired
private EngineerSliceUsedCapacityDao engineerSliceUsedCapacityDao;
private boolean verifyCalendar(List<CapacityEngineerCalendarEntity> configs) {
// 检查多条请假配置是否有交叉行为; configs已经根据startTime排序
......@@ -84,19 +98,99 @@ public class InitEngineerCapacityScheduler {
return new CapacityStats().setTotal(totalWorkTime).setUsed(totalLeaveTime).setRemain(totalWorkTime - totalLeaveTime);
}
private void initOneEngineer(String date, String engineerCode) {
log.info("正在处理日期[{}] 技术员[{}]", date, engineerCode);
// 初始化一个工程师、一天的容量
CapacityEngineerStatEntity statEntity = capacityEngineerStatDao.getByWorkdayAndEngineerCode(date, engineerCode);
if (statEntity != null && !rewriteForce) {
log.error("技术员容量信息记录已存在, 直接返回");
private void initOneEngineerSlice(String date, String engineerCode, List<CapacityEngineerCalendarEntity> configs,
List<TimeSliceEntity> commonTimeSliceList) {
//查询时间片容量
List<CapacityEngineerSliceUsedEntity> engineerTimeSlice = engineerSliceUsedCapacityDao.findByWorkdayAndEngineerCode(date, engineerCode);
if (!CollectionUtil.isEmpty(engineerTimeSlice) && !rewriteForce) {
log.warn("工程师:{}存在日期:{}时间切片记录, 无需初始化", engineerCode, date);
return;
}
// 查询工程师正常的工作时间 并按小时切片:
EngineerBusinessEntity businessEntity = engineerBusinessDao.getByEngineerCode(engineerCode);
LocalDateTime workStartTime = DateUtils.localDateTimeFromStr(String.format("%s %s:00", date, businessEntity.getWorkOn()));
LocalDateTime workEndTime = DateUtils.localDateTimeFromStr(String.format("%s %s:00", date, businessEntity.getWorkOff()));
List<TimeSliceEntity> timeCorridor = getTimeSliceEntities(workStartTime, workEndTime, commonTimeSliceList);
ArrayList<CapacityEngineerSliceUsedEntity> resultList = new ArrayList<>(timeCorridor.size());
for (TimeSliceEntity timeSlice : timeCorridor) {
CapacityEngineerSliceUsedEntity r = new CapacityEngineerSliceUsedEntity();
r.setTimmeSlice(timeSlice);
r.setEngineerCode(engineerCode);
r.setWorkday(date);
LocalTime sliceStartLocalTime = LocalTime.parse(timeSlice.getStart(), DateUtil.TIME_FORMATTER);
LocalTime sliceEndLocalTime = LocalTime.parse(timeSlice.getEnd(), DateUtil.TIME_FORMATTER);
r.setCapTotal(DateTimeUtil.betweenTwoTime(sliceStartLocalTime, sliceEndLocalTime, TimeUnit.MINUTES));
//校验是否有在当前时间段的请假时间
//已用容量
long lengthOfLeave = 0L;
for (int i = 0; i < configs.size(); i++) {
CapacityEngineerCalendarEntity leave = configs.get(i);
LocalTime leaveStartTime = leave.getStartTime().toLocalTime();
LocalTime leaveEndTime = leave.getEndTime().toLocalTime();
boolean startIn = DateTimeUtil.isIn(leaveStartTime, sliceStartLocalTime, sliceEndLocalTime);
boolean endAfter = leaveEndTime.isAfter(sliceEndLocalTime);
//8:00- 8:30 8:00 - 10:30
//请假时间仅落在当前时间段内, 当前时间段请假时长为 请假结束时间- 请假开始时间
if (startIn && DateTimeUtil.isIn(leaveEndTime, sliceStartLocalTime, sliceEndLocalTime)) {
lengthOfLeave += DateTimeUtil.betweenTwoTime(leaveStartTime, leaveEndTime, TimeUnit.MINUTES);
} else if (startIn && endAfter) {
//落在当前时间段和下一个时间段
lengthOfLeave += DateTimeUtil.betweenTwoTime(leaveStartTime, sliceEndLocalTime, TimeUnit.MINUTES);
} else if (leaveStartTime.isBefore(sliceStartLocalTime) && leaveEndTime.isAfter(sliceEndLocalTime)) {
lengthOfLeave += DateTimeUtil.betweenTwoTime(sliceStartLocalTime, sliceEndLocalTime, TimeUnit.MINUTES);
}
}
// 剩余可约容量
long leftUseTime = 60 - lengthOfLeave;
r.setCapLeft(leftUseTime);
r.setCapUsed(lengthOfLeave);
r.setCreateTime(LocalDateTime.now());
r.setUpdateTime(LocalDateTime.now());
resultList.add(r);
}
engineerSliceUsedCapacityDao.saveAll(resultList);
}
private List<TimeSliceEntity> getTimeSliceEntities(LocalDateTime workStartTime, LocalDateTime workEndTime,
List<TimeSliceEntity> commonTimeSliceList) {
//切片开始时间
LocalTime sliceStartHour = LocalTime.of(workStartTime.getHour(), 0);
//切片结束时间
LocalTime sliceEndHour = LocalTime.of(workEndTime.getHour(), 0);
List<TimeSliceEntity> timeCorridor = commonTimeSliceList.stream()
.filter(slice -> {
LocalTime startLocalTime = LocalTime.parse(slice.getStart(), DateUtil.TIME_FORMATTER);
LocalTime endLocalTime = LocalTime.parse(slice.getEnd(), DateUtil.TIME_FORMATTER);
return (startLocalTime.isAfter(sliceStartHour) && endLocalTime.isBefore(sliceEndHour)) ||
(startLocalTime.equals(sliceStartHour) || endLocalTime.equals(sliceEndHour));
}).sorted(Comparator.comparing(TimeSliceEntity::getId)).collect(Collectors.toList());
return timeCorridor;
}
private void initOneEngineer(String date, String engineerCode) {
log.info("正在处理日期[{}] 技术员[{}]", date, engineerCode);
List<CapacityEngineerCalendarEntity> configs = capacityEngineerCalendarDao.findCalendarByWorkdayAndEngineerCode(date, engineerCode)
.stream().sorted(Comparator.comparing(CapacityEngineerCalendarEntity::getStartTime)).collect(Collectors.toList());
if (!configs.isEmpty() && !verifyCalendar(configs)) {
log.error("配置检查失败,忽略退出");
log.error("日期[{}]技术员[{}]请假配置检查失败,忽略退出", date, engineerCode);
return;
}
List<TimeSliceEntity> commonTimeSliceList = timeSliceDao.findByType("HOURS");
//初始化一个工程师时间切片容量
initOneEngineerSlice(date, engineerCode, configs, commonTimeSliceList);
// 初始化一个工程师、一天的容量
CapacityEngineerStatEntity statEntity = capacityEngineerStatDao.getByWorkdayAndEngineerCode(date, engineerCode);
if (statEntity != null && !rewriteForce) {
log.error("技术员容量信息记录已存在, 直接返回");
return;
}
String memo = configs.stream().map(CapacityEngineerCalendarEntity::getType).collect(Collectors.joining("/"));
......
......@@ -2,7 +2,70 @@ package com.dituhui.pea.order.service;
import com.dituhui.pea.common.Result;
import com.dituhui.pea.order.dto.CapacityOrderQueryDTO;
import com.dituhui.pea.order.dto.param.CapacityQueryDTO;
import com.dituhui.pea.order.dto.param.Location;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.List;
public interface CapacityQueryService {
Result<?> queryMatchCapacityData(CapacityOrderQueryDTO.Request capacityQueryReqDTO);
/**
* 对外创单可用容量查询
*
* @param services 服务技能信息, 包含品牌, 产品类型, 服务技能
* @param location 需要查询容量的地址
* @param beginDate 开始日期
* @param endDate 结束日期
* @return 满足对应技能工作队的容量(三种类型与分站绑定)
*/
Result<CapacityQueryDTO.Result> matchCapacityData(List<CapacityQueryDTO.Service> services, Location location, LocalDate beginDate, LocalDate endDate);
/**
* 根据工作队,时间段,技能、品牌,服务类型查容量
*
* @param teamId 工作队
* @param service 服务技能信息, 包含品牌, 产品类型, 服务技能--汉字
* @param targetDate 时间段对应的日期
* @param startTime 开始时间
* @param endTime 结束时间
* @return 日期对应时间段工作队容量
*/
CapacityQueryDTO.Segment queryCapacityByTeam(String teamId, CapacityQueryDTO.Service service, LocalDate targetDate,
LocalTime startTime, LocalTime endTime);
/**
* 根据工作队,日期,技能、品牌,服务类型查容量
*
* @param teamId 工作队
* @param service 服务技能信息, 包含品牌, 产品类型, 服务技能--汉字
* @param targetDate 查询容量的日期
* @return 工作队指定日期的容量
*/
CapacityQueryDTO.Segment queryCapacityByTeam(String teamId, CapacityQueryDTO.Service service, LocalDate targetDate);
/**
* 查询单个工程师指定日期的容量状态
*
* @param engineerCode 工程师编码
* @param date 需要查询的日期
* @return 返回单个工程师容量结果
*/
CapacityQueryDTO.Segment queryEngineerCapacity(String engineerCode, LocalDate date);
/**
* 查询单个工程师指定日期时间段内的的容量状态
*
* @param engineerCode 工程师编码
* @param date 需要查询的日期
* @param startTime 开始时间
* @param endTime 结束时间
* @return 返回单个工程师容量结果
*/
CapacityQueryDTO.Segment queryEngineerCapacity(String engineerCode, CapacityQueryDTO.Service service,
LocalDate date, LocalTime startTime, LocalTime endTime);
}
package com.dituhui.pea.order.service.impl;
import com.dituhui.pea.common.Result;
import com.dituhui.pea.enums.StatusCodeEnum;
import com.dituhui.pea.order.common.CapacityUtils;
import com.dituhui.pea.order.common.DateSplit;
import com.dituhui.pea.order.common.DateUtils;
import com.dituhui.pea.order.common.EngineerUtils;
import com.dituhui.pea.order.common.SaasUtils;
import com.dituhui.pea.order.common.jackson.DateTimeUtil;
import com.dituhui.pea.order.common.jackson.DateUtil;
import com.dituhui.pea.order.dao.CapacityEngineerStatDao;
import com.dituhui.pea.order.dao.EngineerInfoDao;
import com.dituhui.pea.order.dao.EngineerSkillDao;
import com.dituhui.pea.order.dao.EngineerSliceUsedCapacityDao;
import com.dituhui.pea.order.dao.MapBlockInfoDao;
import com.dituhui.pea.order.dao.OrgTeamDao;
import com.dituhui.pea.order.dao.SkillInfoDao;
import com.dituhui.pea.order.dao.TimeSliceDao;
import com.dituhui.pea.order.dto.CapacityOrderQueryDTO;
import com.dituhui.pea.order.entity.*;
import com.dituhui.pea.order.dto.param.CapacityQueryDTO;
import com.dituhui.pea.order.dto.param.Location;
import com.dituhui.pea.order.entity.CapacityEngineerSliceUsedEntity;
import com.dituhui.pea.order.entity.CapacityEngineerStatEntity;
import com.dituhui.pea.order.entity.EngineerInfoEntity;
import com.dituhui.pea.order.entity.EngineerSkillEntity;
import com.dituhui.pea.order.entity.MapBlockInfoEntity;
import com.dituhui.pea.order.entity.OrgTeamEntity;
import com.dituhui.pea.order.entity.SkillInfoEntity;
import com.dituhui.pea.order.entity.TimeSliceEntity;
import com.dituhui.pea.order.feign.ISaaSRemoteService;
import com.dituhui.pea.order.service.CapacityQueryService;
import com.dituhui.pea.pojo.fendan.FendanDTO;
import com.google.common.collect.Sets;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.OptionalLong;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
......@@ -47,6 +73,196 @@ public class CapacityQueryServiceImpl implements CapacityQueryService {
@Autowired
ISaaSRemoteService saasRemoteService;
@Autowired
private EngineerInfoDao engineerInfoDao;
@Autowired
private MapBlockInfoDao mapBlockInfoDao;
@Autowired
private TimeSliceDao timeSliceDao;
@Autowired
private EngineerSliceUsedCapacityDao engineerSliceUsedCapacityDao;
@Override
public Result<CapacityQueryDTO.Result> matchCapacityData(List<CapacityQueryDTO.Service> services, Location location, LocalDate beginDate, LocalDate endDate) {
//1:根据经纬度分单获取面
FendanDTO fendanDTO = new FendanDTO();
final String addressLongLat = location.getLongitude() + "," + location.getLatitude();
fendanDTO.setXy(addressLongLat);
List<SaasUtils.BlockInfo> blockInfoList = saasUtils.queryBlocksByXy(fendanDTO);
if (blockInfoList.isEmpty()) {
return Result.failed(StatusCodeEnum.FENDAN_AREA_UNMATCHED);
}
//2:根据查询出区划匹配分站信息
List<String> blockIds = blockInfoList.stream().map(SaasUtils.BlockInfo::getBlockId).distinct().collect(Collectors.toList());
List<MapBlockInfoEntity> mapBlockInfoEntities = mapBlockInfoDao.findByAreaIdsIn(blockIds);
if (CollectionUtils.isEmpty(mapBlockInfoEntities)) {
return Result.failed(StatusCodeEnum.FENDAN_TEAM_UNMATCHED);
}
//分站、网点ID
List<String> groupIdList = mapBlockInfoEntities.stream().map(MapBlockInfoEntity::getGroupId).distinct()
.collect(Collectors.toList());
final String groupId = groupIdList.get(0);
//3. 查询分站下拥有对应技能的工程师(排重) 技能码转换为对应的技能汉字
List<SkillInfoEntity> querySkillGroup = new ArrayList<>();
Set<EngineerInfoEntity> allFulfillEngineer = Sets.newConcurrentHashSet();
for (CapacityQueryDTO.Service service : services) {
Set<EngineerInfoEntity> engineerInfoEntities = engineerInfoDao.listByBrandAndTypeAndSkillAndGroupId(service.getBrand(),
service.getProductType(), service.getServiceType(), groupId);
//查询对应的技能信息
SkillInfoEntity skillInfo = skillInfoDao.getByBrandAndTypeAndSkill(service.getBrand(),
service.getProductType(), service.getServiceType());
querySkillGroup.add(skillInfo);
allFulfillEngineer.addAll(engineerInfoEntities);
}
log.info("[matchCapacityData]【符合技能要求的工程师总数为:{} 个】", allFulfillEngineer.size());
//计算所有查询技能的所需耗时 (querySkillGroup)
final int totalTakeTime = querySkillGroup.stream()
.mapToInt(SkillInfoEntity::getTakeTime)
.sum();
//查询单个工程师日期范围内的技能容量信息 后加和汇总
List<TimeSliceEntity> halfDayTypeTimeSlice = timeSliceDao.findByType("HALF_DAY");
List<TimeSliceEntity> allDayTypeTimeSlice = timeSliceDao.findByType("ALL_DAY");
LocalDate currentDate = beginDate;
//定义返回值
CapacityQueryDTO.Result result = new CapacityQueryDTO.Result();
result.setGroupId(groupId);
result.setTakeTime(totalTakeTime);
List<CapacityQueryDTO.Data> datas = new ArrayList<>();
while (!currentDate.isAfter(endDate)) {
List<CapacityEngineerSliceUsedEntity> allEngineerTimeSlice = new ArrayList<>();
for (EngineerInfoEntity engineerInfo : allFulfillEngineer) {
List<CapacityEngineerSliceUsedEntity> engineerTimeSlice = engineerSliceUsedCapacityDao
.findByWorkdayAndEngineerCode(DateTimeUtil.formatDate(currentDate), engineerInfo.getEngineerCode());
allEngineerTimeSlice.addAll(engineerTimeSlice);
}
//计算所有工程师半天类型容量
List<CapacityQueryDTO.Segment> engineerHalfDay = getEngineerTypeDay(halfDayTypeTimeSlice,
allEngineerTimeSlice, currentDate, totalTakeTime);
//计算所有工程师全天天类型容量
List<CapacityQueryDTO.Segment> engineerAllDay = getEngineerTypeDay(allDayTypeTimeSlice,
allEngineerTimeSlice, currentDate, totalTakeTime);
//TODO 时间段
CapacityQueryDTO.Data data = new CapacityQueryDTO.Data();
data.setDate(DateUtil.toDate(currentDate));
List<CapacityQueryDTO.Segment> objects = new ArrayList<>();
objects.addAll(engineerHalfDay);
objects.addAll(engineerAllDay);
data.setSegments(objects);
datas.add(data);
currentDate = currentDate.plusDays(1);
}
result.setDatas(datas);
return Result.success(result);
}
private List<CapacityQueryDTO.Segment> getEngineerTypeDay(List<TimeSliceEntity> typeTimeSlice,
List<CapacityEngineerSliceUsedEntity> engineerTimeSlice,
LocalDate date, int totalTakeTime) {
ArrayList<CapacityQueryDTO.Segment> segments = new ArrayList<>();
//半天容量
for (TimeSliceEntity targetTimeSlice : typeTimeSlice) {
final LocalTime targetStartTime = LocalTime.parse(targetTimeSlice.getStart(), DateUtil.TIME_FORMATTER);
final LocalTime targetEndTime = LocalTime.parse(targetTimeSlice.getEnd(), DateUtil.TIME_FORMATTER);
CapacityQueryDTO.Segment segment = caculateTargetTimeSlice(totalTakeTime, targetTimeSlice.getName(),
engineerTimeSlice, targetStartTime, targetEndTime, date);
segments.add(segment);
}
return segments;
}
private static CapacityQueryDTO.Segment caculateTargetTimeSlice(int totalTakeTime, String timeSliceName,
List<CapacityEngineerSliceUsedEntity> engineerTimeSlice, LocalTime targetStartTime,
LocalTime targetEndTime, LocalDate targetDate) {
List<CapacityEngineerSliceUsedEntity> collect = engineerTimeSlice.stream().filter(t -> {
TimeSliceEntity timeSlice = t.getTimmeSlice();
LocalTime sliceStartHour = LocalTime.parse(timeSlice.getStart(), DateUtil.TIME_FORMATTER);
LocalTime sliceEndHour = LocalTime.parse(timeSlice.getEnd(), DateUtil.TIME_FORMATTER);
return (targetStartTime.isAfter(sliceStartHour) && targetEndTime.isBefore(sliceEndHour)) ||
(targetStartTime.equals(sliceStartHour) || targetEndTime.equals(sliceEndHour));
}).collect(Collectors.toList());
CapacityQueryDTO.Segment segment = new CapacityQueryDTO.Segment();
segment.setName(timeSliceName);
segment.setEndTime(DateUtil.toDate(LocalDateTime.of(targetDate, targetEndTime)));
segment.setBeginTime(DateUtil.toDate(LocalDateTime.of(targetDate, targetStartTime)));
if (!CollectionUtils.isEmpty(collect)) {
OptionalLong optionalLong = collect.stream().mapToLong(t -> {
Long maxDuration = t.getMaxDuration();
return Objects.isNull(maxDuration) ? 0L : maxDuration;
}).max();
long maxDuration = optionalLong.isEmpty() ? 0 : optionalLong.getAsLong();
segment.setMaxDuration(maxDuration);
long remain = collect.stream().mapToLong(CapacityEngineerSliceUsedEntity::getCapLeft).sum();
segment.setRemain(remain);
segment.setStatus(totalTakeTime <= maxDuration ? 1 : 0);
} else {
segment.setMaxDuration(0);
segment.setRemain(0);
segment.setStatus(0);
}
return segment;
}
@Override
public CapacityQueryDTO.Segment queryCapacityByTeam(String teamId, CapacityQueryDTO.Service service,
LocalDate targetDate, LocalTime startTime, LocalTime endTime) {
Set<EngineerInfoEntity> engineerInfoEntities = engineerInfoDao.listByBrandAndTypeAndSkillAndTeamId(service.getBrand(),
service.getProductType(), service.getServiceType(), teamId);
//查询对应的技能信息
SkillInfoEntity skillInfo = skillInfoDao.getByBrandAndTypeAndSkill(service.getBrand(),
service.getProductType(), service.getServiceType());
List<CapacityEngineerSliceUsedEntity> allEngineerTimeSlice = new ArrayList<>();
for (EngineerInfoEntity engineerInfo : engineerInfoEntities) {
List<CapacityEngineerSliceUsedEntity> engineerTimeSlice = engineerSliceUsedCapacityDao
.findByWorkdayAndEngineerCode(DateTimeUtil.formatDate(targetDate), engineerInfo.getEngineerCode());
allEngineerTimeSlice.addAll(engineerTimeSlice);
}
log.info("[matchCapacityData]【符合技能要求的工程师总数为:{} 个】", engineerInfoEntities.size());
return caculateTargetTimeSlice(skillInfo.getTakeTime(), "时间段", allEngineerTimeSlice, startTime, endTime, targetDate);
}
@Override
public CapacityQueryDTO.Segment queryCapacityByTeam(String teamId, CapacityQueryDTO.Service service, LocalDate targetDate) {
//获取工作队信息
OrgTeamEntity team = orgTeamDao.getByTeamId(teamId);
String workOn = String.format("%s:00", team.getWorkOn());
String workOff = String.format("%s:00", team.getWorkOff());
LocalTime startTime = LocalTime.parse(workOn, DateUtil.TIME_FORMATTER);
LocalTime endTime = LocalTime.parse(workOff, DateUtil.TIME_FORMATTER);
return queryCapacityByTeam(teamId, service, targetDate, startTime, endTime);
}
@Override
public CapacityQueryDTO.Segment queryEngineerCapacity(String engineerCode, LocalDate date) {
return null;
}
@Override
public CapacityQueryDTO.Segment queryEngineerCapacity(String engineerCode, CapacityQueryDTO.Service service,
LocalDate date, LocalTime startTime, LocalTime endTime) {
List<CapacityEngineerSliceUsedEntity> engineerTimeSlice = engineerSliceUsedCapacityDao
.findByWorkdayAndEngineerCode(DateTimeUtil.formatDate(date), engineerCode);
//查询对应的技能信息
SkillInfoEntity skillInfo = skillInfoDao.getByBrandAndTypeAndSkill(service.getBrand(),
service.getProductType(), service.getServiceType());
return caculateTargetTimeSlice(skillInfo.getTakeTime(), "时间段", engineerTimeSlice, startTime, endTime, date);
}
@Override
public Result<?> queryMatchCapacityData(CapacityOrderQueryDTO.Request reqDTO) {
// 单条容量查询
......@@ -63,7 +279,7 @@ public class CapacityQueryServiceImpl implements CapacityQueryService {
}
if (blockIds.isEmpty()) {
log.error("分单接口没有查到对应的结果");
return Result.failed(String.format("分单接口(address:%s)(location:%s) 没有查到配置区块", reqDTO.getAddress(), reqDTO.getLocation()));
return Result.failed(String.format("分单接口(address:%s)(location:%s) 没有查到配置区块", reqDTO.getAddress(), reqDTO.getLocation()));
}
List<String> teamIds = capacityUtils.getTeamIdsByBlockIdsAndLayerIds(blockIds, layerIds);
if (teamIds == null || teamIds.isEmpty()) {
......@@ -180,11 +396,25 @@ public class CapacityQueryServiceImpl implements CapacityQueryService {
return calendar;
}
private String fixBrand(String brand){
if (!brand.equals("嘉格纳")){
private String fixBrand(String brand) {
if (!brand.equals("嘉格纳")) {
return "博世/西门子以及其他品牌";
} else {
return brand;
}
}
/**
* 进行时间切片工作
*
* @return 返回相邻间隔的时间段, 相邻时间片跨度一小时, 时间片跨度自定义
*/
private List<TimeSliceEntity> sliceTime(LocalDate date, LocalTime startTime, LocalTime endTime) {
LocalDateTime start = LocalDateTime.of(date, startTime);
LocalDateTime end = LocalDateTime.of(date, endTime);
List<DateSplit> dateSplits = DateUtil.splitByHour(DateUtil.toDate(start), DateUtil.toDate(end), 2, 1);
return Collections.emptyList();
}
}
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!