Commit 3e5f4cc8 by 刘鑫

Merge branch 'develop-16542' into 'develop'

容量逻辑

See merge request !347
2 parents 5b510ce2 5c3dbacc
Showing with 833 additions and 48 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,28 @@ public class DateTimeUtil {
return result;
}
public static void main(String[] args) {
long l = betweenTwoTime( LocalTime.of(8, 0),
LocalTime.of(9, 0), TimeUnit.MINUTES);
System.out.printf(""+l);
}
/**
* 当前时间是否在时间指定范围内, 包含起始时间<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,89 @@ 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;
}
/**
* 按照小时切割时间区间
*/
public static List<DateSplit> splitByHour(Date startTime, Date endTime, int intervalHours) {
if (endTime.getTime() <= startTime.getTime()) {
return Collections.emptyList();
}
List<DateSplit> dateSplits = new ArrayList<>(24);
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, intervalHours);
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,30 @@ 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);
@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 +69,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 +77,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);
}
......@@ -136,7 +136,7 @@ public class CapacityQueryDTO {
/**
* 最大可用时长, 单位: 分钟
*/
private int maxDuration;
private long maxDuration;
/**
* 容量名称 全天/上午/下午/时间段
......@@ -165,7 +165,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,57 @@ 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 services 服务技能信息, 包含品牌, 产品类型, 服务技能--汉字
* @param targetDate 时间段对应的日期
* @param startTime 开始时间
* @param endTime 结束时间
* @return 日期对应时间段工作队容量
*/
CapacityQueryDTO.Segment queryCapacityByTeam(String teamId, List<CapacityQueryDTO.Service> services, LocalDate targetDate,
LocalTime startTime, LocalTime endTime);
/**
* 根据工作队,日期,技能、品牌,服务类型查容量
*
* @param teamId 工作队
* @param services 服务技能信息, 包含品牌, 产品类型, 服务技能--汉字
* @param targetDate 查询容量的日期
* @return 工作队指定日期的容量
*/
CapacityQueryDTO.Segment queryCapacityByTeam(String teamId, List<CapacityQueryDTO.Service> services, LocalDate targetDate);
/**
* 查询单个工程师指定日期的容量状态
*
* @param engineerCode 工程师编码
* @param date 需要查询的日期
* @return 返回单个工程师容量结果
*/
CapacityQueryDTO.Segment queryEngineerCapacity(String engineerCode, LocalDate date);
}
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!