Compare commits
59 Commits
09f1adb63b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 9ce5cb8f2e | |||
| ccf87c06ff | |||
| 156602fd59 | |||
| 2752a31a49 | |||
| 40fdd14d13 | |||
| f1158d1e16 | |||
| 6055f06f83 | |||
| a0d8f459e4 | |||
| 5672b1c07a | |||
| 89f47860a7 | |||
| 7f9ae18022 | |||
| f5b2ddb743 | |||
| 1e128cecfe | |||
| 534a64a874 | |||
| c412f73b80 | |||
| 28a37f4105 | |||
| 975c57f12b | |||
| 67d4519462 | |||
| dc67788f51 | |||
| 04c84a3ed3 | |||
| acf0048bf1 | |||
| 2ac3901f75 | |||
| 3613b6d83a | |||
| 4faad94c79 | |||
| f6b5940a27 | |||
| db90e2a084 | |||
| f48818b14d | |||
| 602928dc0b | |||
| 455f3bbf09 | |||
| e0e31c765b | |||
| 335dc88a2a | |||
| 50527f68e0 | |||
| 8b78e82a80 | |||
| 8b3e016568 | |||
| c1c3fdba68 | |||
| 2d86713971 | |||
| f3d5556196 | |||
| f831f29b63 | |||
| 03b0e20301 | |||
| ba5796984c | |||
| a69c1f0cb2 | |||
| e728a98dcc | |||
| 54b820cc40 | |||
| f73a002f0f | |||
| 0400398361 | |||
| edca68136c | |||
| 1584d7e06d | |||
| 7c261c3028 | |||
| 5d0c056449 | |||
| fd72c18d48 | |||
| dfd912bf07 | |||
| 8b627c000f | |||
| 56f7a6abb9 | |||
| ebb57f4f26 | |||
| 365fc08b62 | |||
| 50f3f15f48 | |||
| 9c64dd8451 | |||
| 5e25d68faf | |||
| d4c7138dc3 |
@@ -0,0 +1,34 @@
|
||||
package com.ruoyi.fadapp.controller;
|
||||
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.fadapp.service.IFadAppLocationService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* FAD APP 定位控制器
|
||||
*/
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/fadapp/location")
|
||||
public class FadAppLocationController {
|
||||
|
||||
private final IFadAppLocationService locationService;
|
||||
|
||||
/**
|
||||
* 根据经纬度获取城市
|
||||
*
|
||||
* @param latitude 纬度
|
||||
* @param longitude 经度
|
||||
* @return 城市名称
|
||||
*/
|
||||
@GetMapping("/city")
|
||||
public R<String> getCity(@RequestParam Double latitude, @RequestParam Double longitude) {
|
||||
return R.ok(locationService.getCityByLocation(latitude, longitude));
|
||||
}
|
||||
}
|
||||
@@ -11,9 +11,18 @@ import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
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.NotNull;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* FAD APP用户管理Controller
|
||||
@@ -26,6 +35,7 @@ import javax.validation.constraints.NotNull;
|
||||
@RequestMapping("/fadapp/user")
|
||||
public class FadAppUserController extends BaseController {
|
||||
|
||||
private static final String IPSTACK_ACCESS_KEY = "";
|
||||
private final IFadAppUserService userService;
|
||||
|
||||
/**
|
||||
@@ -63,4 +73,56 @@ public class FadAppUserController extends BaseController {
|
||||
public R<Object> getUserStats() {
|
||||
return R.ok(userService.getUserStats());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据经纬度获取城市
|
||||
*/
|
||||
@GetMapping("/location/city")
|
||||
public R<Object> getCityByLocation(@RequestParam Double latitude,
|
||||
@RequestParam Double longitude) {
|
||||
return R.ok(resolveCity(latitude, longitude));
|
||||
}
|
||||
|
||||
private String resolveCity(Double latitude, Double longitude) {
|
||||
if (latitude == null || longitude == null) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
String urlStr = "https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat="
|
||||
+ URLEncoder.encode(String.valueOf(latitude), StandardCharsets.UTF_8.name())
|
||||
+ "&lon=" + URLEncoder.encode(String.valueOf(longitude), StandardCharsets.UTF_8.name())
|
||||
+ "&zoom=10&addressdetails=1";
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(urlStr).openConnection();
|
||||
connection.setRequestMethod("GET");
|
||||
connection.setConnectTimeout(5000);
|
||||
connection.setReadTimeout(5000);
|
||||
connection.setRequestProperty("User-Agent", "FAD-App/1.0");
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
result.append(line);
|
||||
}
|
||||
return extractCity(result.toString());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private String extractCity(String json) {
|
||||
if (json == null || json.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
String[] keys = new String[]{"city", "town", "village", "county", "state"};
|
||||
for (String key : keys) {
|
||||
Pattern pattern = Pattern.compile("\\\"" + key + "\\\"\\s*:\\s*\\\"([^\\\"]+)\\\"");
|
||||
Matcher matcher = pattern.matcher(json);
|
||||
if (matcher.find()) {
|
||||
return matcher.group(1);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.ruoyi.fadapp.service;
|
||||
|
||||
/**
|
||||
* FAD APP 定位服务接口
|
||||
*/
|
||||
public interface IFadAppLocationService {
|
||||
|
||||
/**
|
||||
* 根据经纬度获取城市
|
||||
*
|
||||
* @param latitude 纬度
|
||||
* @param longitude 经度
|
||||
* @return 城市名称
|
||||
*/
|
||||
String getCityByLocation(Double latitude, Double longitude);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.ruoyi.fadapp.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.ruoyi.fadapp.service.IFadAppLocationService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* FAD APP 定位服务实现
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class FadAppLocationServiceImpl implements IFadAppLocationService {
|
||||
|
||||
private static final String AMAP_REVERSE_URL = "https://restapi.amap.com/v3/geocode/regeo";
|
||||
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
@Value("${fad.amap.key:}")
|
||||
private String amapKey;
|
||||
|
||||
@Override
|
||||
public String getCityByLocation(Double latitude, Double longitude) {
|
||||
if (latitude == null || longitude == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!org.springframework.util.StringUtils.hasText(amapKey)) {
|
||||
log.warn("高德地图 key 未配置");
|
||||
return "";
|
||||
}
|
||||
|
||||
try {
|
||||
Map<String, Object> params = new LinkedHashMap<>();
|
||||
params.put("key", amapKey);
|
||||
params.put("location", longitude + "," + latitude);
|
||||
params.put("extensions", "base");
|
||||
params.put("batch", "false");
|
||||
params.put("roadlevel", 0);
|
||||
params.put("output", "JSON");
|
||||
|
||||
StringBuilder url = new StringBuilder(AMAP_REVERSE_URL).append("?");
|
||||
params.forEach((key, value) -> url.append(key).append("=").append(value).append("&"));
|
||||
url.setLength(url.length() - 1);
|
||||
|
||||
JSONObject response = restTemplate.getForObject(url.toString(), JSONObject.class);
|
||||
if (response == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!"1".equals(response.getString("status"))) {
|
||||
log.warn("高德逆地理编码失败: {}", response);
|
||||
return "";
|
||||
}
|
||||
|
||||
JSONObject regeocode = response.getJSONObject("regeocode");
|
||||
if (regeocode == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
JSONObject addressComponent = regeocode.getJSONObject("addressComponent");
|
||||
if (addressComponent == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String city = addressComponent.getString("city");
|
||||
if (org.springframework.util.StringUtils.hasText(city)) {
|
||||
return city;
|
||||
}
|
||||
|
||||
String province = addressComponent.getString("province");
|
||||
if (org.springframework.util.StringUtils.hasText(province)) {
|
||||
return province;
|
||||
}
|
||||
|
||||
return "";
|
||||
} catch (Exception e) {
|
||||
log.warn("根据经纬度获取城市失败, latitude={}, longitude={}, err={}", latitude, longitude, e.getMessage());
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
3
fad-app/src/main/resources/application.yml
Normal file
3
fad-app/src/main/resources/application.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
fad:
|
||||
amap:
|
||||
key: 978ae5bc551f57d172d3e397af5a6f67
|
||||
@@ -6,9 +6,12 @@ import com.ruoyi.common.core.domain.PageQuery;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.common.helper.LoginHelper;
|
||||
import com.ruoyi.hrm.domain.bo.HrmAppropriationReqBo;
|
||||
import com.ruoyi.hrm.domain.vo.HrmAppropriationReqVo;
|
||||
import com.ruoyi.hrm.domain.vo.HrmInvoiceOcrResultVo;
|
||||
import com.ruoyi.hrm.service.IHrmAppropriationReqService;
|
||||
import com.ruoyi.hrm.service.IHrmInvoiceOcrService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@@ -28,9 +31,11 @@ import java.util.List;
|
||||
public class HrmAppropriationReqController extends BaseController {
|
||||
|
||||
private final IHrmAppropriationReqService service;
|
||||
private final IHrmInvoiceOcrService invoiceOcrService;
|
||||
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<HrmAppropriationReqVo> list(HrmAppropriationReqBo bo, PageQuery pageQuery) {
|
||||
bo.setCreateBy(LoginHelper.getUsername());
|
||||
return service.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
@@ -59,7 +64,18 @@ public class HrmAppropriationReqController extends BaseController {
|
||||
|
||||
@GetMapping("/all")
|
||||
public R<List<HrmAppropriationReqVo>> all(HrmAppropriationReqBo bo) {
|
||||
bo.setCreateBy(String.valueOf(LoginHelper.getUserId()));
|
||||
return R.ok(service.queryList(bo));
|
||||
}
|
||||
|
||||
@GetMapping("/ocr-health")
|
||||
public R<Boolean> ocrHealth() {
|
||||
return R.ok(invoiceOcrService.isAlive());
|
||||
}
|
||||
|
||||
@PostMapping("/ocr-by-oss")
|
||||
public R<HrmInvoiceOcrResultVo> ocrByOss(@RequestParam @NotNull Long ossId) {
|
||||
return R.ok(invoiceOcrService.recognizeByOssId(ossId));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -64,11 +64,9 @@ public class HrmEmployeeController extends BaseController {
|
||||
*/
|
||||
@GetMapping("/byUserId/{userId}")
|
||||
public R<HrmEmployeeVo> getByUserId(@PathVariable @NotNull Long userId) {
|
||||
HrmEmployeeBo bo = new HrmEmployeeBo();
|
||||
bo.setUserId(userId);
|
||||
List<HrmEmployeeVo> list = service.queryList(bo);
|
||||
if (list != null && !list.isEmpty()) {
|
||||
return R.ok(list.get(0));
|
||||
HrmEmployeeVo vo = service.queryByUserId(userId);
|
||||
if (vo != null) {
|
||||
return R.ok(vo);
|
||||
}
|
||||
return R.fail("未找到该用户对应的员工信息");
|
||||
}
|
||||
|
||||
@@ -66,7 +66,14 @@ public class HrmFlowCcController extends BaseController {
|
||||
Long userId = LoginHelper.getUserId();
|
||||
return toAjax(service.markRead(ccId, userId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记抄送未读(新增)
|
||||
*/
|
||||
@PostMapping("/{ccId}/unread")
|
||||
public R<Void> unread(@PathVariable Long ccId) {
|
||||
Long userId = LoginHelper.getUserId();
|
||||
return toAjax(service.markUnread(ccId, userId));
|
||||
}
|
||||
@GetMapping("/ping")
|
||||
public R<String> ping(@RequestParam @NotNull String x) {
|
||||
return R.ok(x);
|
||||
|
||||
@@ -56,6 +56,16 @@ public class HrmFlowTaskController extends BaseController {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 详情页使用:按 bizType + bizId 查询审批概要、当前待办和历史记录
|
||||
*/
|
||||
@GetMapping("/detailByBiz")
|
||||
public R<?> detailByBiz(@RequestParam @NotNull String bizType,
|
||||
@RequestParam @NotNull Long bizId,
|
||||
@RequestParam(required = false) Long assigneeUserId) {
|
||||
return R.ok(service.queryDetailByBiz(bizType, bizId, assigneeUserId));
|
||||
}
|
||||
|
||||
@GetMapping("/{taskId}")
|
||||
public R<HrmFlowTaskVo> getInfo(@PathVariable @NotNull Long taskId) {
|
||||
return R.ok(service.queryById(taskId));
|
||||
|
||||
@@ -6,7 +6,7 @@ import com.ruoyi.common.core.domain.PageQuery;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.hrm.domain.HrmLeaveReq;
|
||||
import com.ruoyi.common.helper.LoginHelper;
|
||||
import com.ruoyi.hrm.domain.bo.HrmLeaveReqBo;
|
||||
import com.ruoyi.hrm.domain.vo.HrmLeaveReqVo;
|
||||
import com.ruoyi.hrm.domain.vo.HrmLeaveStatsVo;
|
||||
@@ -30,6 +30,7 @@ public class HrmLeaveReqController extends BaseController {
|
||||
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<HrmLeaveReqVo> list(HrmLeaveReqBo bo, PageQuery pageQuery) {
|
||||
bo.setCreateBy(LoginHelper.getUsername());
|
||||
return service.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
@@ -58,6 +59,7 @@ public class HrmLeaveReqController extends BaseController {
|
||||
|
||||
@GetMapping("/all")
|
||||
public R<List<HrmLeaveReqVo>> all(HrmLeaveReqBo bo) {
|
||||
bo.setCreateBy(LoginHelper.getUsername());
|
||||
return R.ok(service.queryList(bo));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
package com.ruoyi.hrm.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.PageQuery;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
||||
import com.ruoyi.common.helper.LoginHelper;
|
||||
import com.ruoyi.hrm.domain.HrmEmployee;
|
||||
import com.ruoyi.hrm.domain.bo.HrmAppropriationReqBo;
|
||||
import com.ruoyi.hrm.domain.bo.HrmLeaveReqBo;
|
||||
import com.ruoyi.hrm.domain.bo.HrmReimburseReqBo;
|
||||
import com.ruoyi.hrm.domain.bo.HrmSealReqBo;
|
||||
import com.ruoyi.hrm.domain.bo.HrmTravelReqBo;
|
||||
import com.ruoyi.hrm.domain.vo.HrmAppropriationReqVo;
|
||||
import com.ruoyi.hrm.domain.vo.HrmLeaveReqVo;
|
||||
import com.ruoyi.hrm.domain.vo.HrmMyApplyVo;
|
||||
import com.ruoyi.hrm.domain.vo.HrmReimburseReqVo;
|
||||
import com.ruoyi.hrm.domain.vo.HrmSealReqVo;
|
||||
import com.ruoyi.hrm.domain.vo.HrmTravelReqVo;
|
||||
import com.ruoyi.hrm.mapper.HrmAppropriationReqMapper;
|
||||
import com.ruoyi.hrm.mapper.HrmEmployeeMapper;
|
||||
import com.ruoyi.hrm.mapper.HrmLeaveReqMapper;
|
||||
import com.ruoyi.hrm.mapper.HrmReimburseReqMapper;
|
||||
import com.ruoyi.hrm.mapper.HrmSealReqMapper;
|
||||
import com.ruoyi.hrm.mapper.HrmTravelReqMapper;
|
||||
import com.ruoyi.system.mapper.SysUserMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/hrm/my-apply")
|
||||
public class HrmMyApplyController extends BaseController {
|
||||
|
||||
private final HrmEmployeeMapper employeeMapper;
|
||||
private final SysUserMapper sysUserMapper;
|
||||
private final HrmLeaveReqMapper leaveReqMapper;
|
||||
private final HrmTravelReqMapper travelReqMapper;
|
||||
private final HrmSealReqMapper sealReqMapper;
|
||||
private final HrmReimburseReqMapper reimburseReqMapper;
|
||||
private final HrmAppropriationReqMapper appropriationReqMapper;
|
||||
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<HrmMyApplyVo> list(String bizType, String status, String keyword, PageQuery pageQuery) {
|
||||
Long currentUserId = LoginHelper.getUserId();
|
||||
if (currentUserId == null) {
|
||||
return TableDataInfo.build();
|
||||
}
|
||||
HrmEmployee emp = employeeMapper.selectOne(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<HrmEmployee>()
|
||||
.eq(HrmEmployee::getUserId, currentUserId));
|
||||
if (emp == null) {
|
||||
return TableDataInfo.build();
|
||||
}
|
||||
SysUser sysUser = sysUserMapper.selectUserById(emp.getUserId());
|
||||
String nickName = sysUser != null ? sysUser.getNickName() : null;
|
||||
|
||||
List<HrmMyApplyVo> all = new ArrayList<>();
|
||||
if (bizType == null || bizType.isEmpty() || "leave".equals(bizType)) {
|
||||
all.addAll(mapLeave(leaveReqMapper.selectVoWithProjectList(buildLeaveBo(emp.getEmpId(), status)), nickName));
|
||||
}
|
||||
if (bizType == null || bizType.isEmpty() || "travel".equals(bizType)) {
|
||||
all.addAll(mapTravel(travelReqMapper.selectVoWithProjectList(buildTravelBo(emp.getEmpId(), status)), nickName));
|
||||
}
|
||||
if (bizType == null || bizType.isEmpty() || "seal".equals(bizType)) {
|
||||
all.addAll(mapSeal(sealReqMapper.selectVoWithProjectList(buildSealBo(emp.getEmpId(), status)), nickName));
|
||||
}
|
||||
if (bizType == null || bizType.isEmpty() || "reimburse".equals(bizType)) {
|
||||
all.addAll(mapReimburse(reimburseReqMapper.selectVoWithProjectList(buildReimburseBo(emp.getEmpId(), status)), nickName));
|
||||
}
|
||||
if (bizType == null || bizType.isEmpty() || "appropriation".equals(bizType)) {
|
||||
all.addAll(mapAppropriation(appropriationReqMapper.selectVoWithProjectList(buildAppropriationBo(emp.getEmpId(), status)), nickName));
|
||||
}
|
||||
|
||||
if (keyword != null && !keyword.isEmpty()) {
|
||||
String lower = keyword.toLowerCase();
|
||||
all = all.stream().filter(v -> contains(v, lower)).collect(Collectors.toList());
|
||||
}
|
||||
all.sort(Comparator.comparing(HrmMyApplyVo::getCreateTime, Comparator.nullsLast(Comparator.naturalOrder())).reversed());
|
||||
|
||||
long start = (pageQuery.getPageNum() - 1L) * pageQuery.getPageSize();
|
||||
long end = Math.min(start + pageQuery.getPageSize(), all.size());
|
||||
Page<HrmMyApplyVo> page = new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize(), all.size());
|
||||
page.setRecords(start >= all.size() ? new ArrayList<>() : all.subList((int) start, (int) end));
|
||||
return TableDataInfo.build(page);
|
||||
}
|
||||
|
||||
private boolean contains(HrmMyApplyVo v, String lower) {
|
||||
return Objects.toString(v.getTitle(), "").toLowerCase().contains(lower)
|
||||
|| Objects.toString(v.getRemark(), "").toLowerCase().contains(lower)
|
||||
|| Objects.toString(v.getNickName(), "").toLowerCase().contains(lower)
|
||||
|| Objects.toString(v.getEmpName(), "").toLowerCase().contains(lower)
|
||||
|| Objects.toString(v.getBizId(), "").contains(lower);
|
||||
}
|
||||
|
||||
private HrmLeaveReqBo buildLeaveBo(Long empId, String status) { HrmLeaveReqBo bo = new HrmLeaveReqBo(); bo.setEmpId(empId); bo.setStatus(status); return bo; }
|
||||
private HrmTravelReqBo buildTravelBo(Long empId, String status) { HrmTravelReqBo bo = new HrmTravelReqBo(); bo.setEmpId(empId); bo.setStatus(status); return bo; }
|
||||
private HrmSealReqBo buildSealBo(Long empId, String status) { HrmSealReqBo bo = new HrmSealReqBo(); bo.setEmpId(empId); bo.setStatus(status); return bo; }
|
||||
private HrmReimburseReqBo buildReimburseBo(Long empId, String status) { HrmReimburseReqBo bo = new HrmReimburseReqBo(); bo.setEmpId(empId); bo.setStatus(status); return bo; }
|
||||
private HrmAppropriationReqBo buildAppropriationBo(Long empId, String status) { HrmAppropriationReqBo bo = new HrmAppropriationReqBo(); bo.setEmpId(empId); bo.setStatus(status); return bo; }
|
||||
|
||||
private List<HrmMyApplyVo> mapLeave(List<HrmLeaveReqVo> list, String nickName) { return list.stream().map(v -> toVo("leave", v.getBizId(), v.getEmpId(), nickName, v.getReason(), v.getStatus(), v.getCreateTime())).collect(Collectors.toList()); }
|
||||
private List<HrmMyApplyVo> mapTravel(List<HrmTravelReqVo> list, String nickName) { return list.stream().map(v -> toVo("travel", v.getBizId(), v.getEmpId(), nickName, v.getReason(), v.getStatus(), v.getCreateTime())).collect(Collectors.toList()); }
|
||||
private List<HrmMyApplyVo> mapSeal(List<HrmSealReqVo> list, String nickName) { return list.stream().map(v -> toVo("seal", v.getBizId(), v.getEmpId(), nickName, v.getRemark(), v.getStatus(), v.getCreateTime())).collect(Collectors.toList()); }
|
||||
private List<HrmMyApplyVo> mapReimburse(List<HrmReimburseReqVo> list, String nickName) { return list.stream().map(v -> toVo("reimburse", v.getBizId(), v.getEmpId(), nickName, v.getReason(), v.getStatus(), v.getCreateTime())).collect(Collectors.toList()); }
|
||||
private List<HrmMyApplyVo> mapAppropriation(List<HrmAppropriationReqVo> list, String nickName) { return list.stream().map(v -> toVo("appropriation", v.getBizId(), v.getEmpId(), nickName, v.getReason(), v.getStatus(), v.getCreateTime())).collect(Collectors.toList()); }
|
||||
|
||||
private HrmMyApplyVo toVo(String bizType, Long bizId, Long empId, String nickName, String title, String status, java.util.Date createTime) {
|
||||
HrmMyApplyVo vo = new HrmMyApplyVo();
|
||||
vo.setBizType(bizType);
|
||||
vo.setBizId(bizId);
|
||||
vo.setEmpId(empId);
|
||||
vo.setNickName(nickName);
|
||||
vo.setTitle(title);
|
||||
vo.setStatus(status);
|
||||
vo.setCreateTime(createTime);
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,11 @@ import com.ruoyi.common.core.domain.PageQuery;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.common.helper.LoginHelper;
|
||||
import com.ruoyi.hrm.domain.bo.HrmReimburseReqBo;
|
||||
import com.ruoyi.hrm.domain.vo.HrmInvoiceOcrResultVo;
|
||||
import com.ruoyi.hrm.domain.vo.HrmReimburseReqVo;
|
||||
import com.ruoyi.hrm.service.IHrmInvoiceOcrService;
|
||||
import com.ruoyi.hrm.service.IHrmReimburseReqService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
@@ -28,9 +31,11 @@ import java.util.List;
|
||||
public class HrmReimburseReqController extends BaseController {
|
||||
|
||||
private final IHrmReimburseReqService service;
|
||||
private final IHrmInvoiceOcrService invoiceOcrService;
|
||||
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<HrmReimburseReqVo> list(HrmReimburseReqBo bo, PageQuery pageQuery) {
|
||||
bo.setCreateBy(LoginHelper.getUsername());
|
||||
return service.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
@@ -59,7 +64,18 @@ public class HrmReimburseReqController extends BaseController {
|
||||
|
||||
@GetMapping("/all")
|
||||
public R<List<HrmReimburseReqVo>> all(HrmReimburseReqBo bo) {
|
||||
bo.setCreateBy(LoginHelper.getUsername());
|
||||
return R.ok(service.queryList(bo));
|
||||
}
|
||||
|
||||
@GetMapping("/ocr-health")
|
||||
public R<Boolean> ocrHealth() {
|
||||
return R.ok(invoiceOcrService.isAlive());
|
||||
}
|
||||
|
||||
@PostMapping("/ocr-by-oss")
|
||||
public R<HrmInvoiceOcrResultVo> ocrByOss(@RequestParam @NotNull Long ossId) {
|
||||
return R.ok(invoiceOcrService.recognizeByOssId(ossId));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.ruoyi.common.core.domain.PageQuery;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.common.helper.LoginHelper;
|
||||
import com.ruoyi.hrm.domain.bo.HrmSealReqBo;
|
||||
import com.ruoyi.hrm.domain.bo.HrmSealStampBo;
|
||||
import com.ruoyi.hrm.domain.vo.HrmSealReqVo;
|
||||
@@ -32,6 +33,7 @@ public class HrmSealReqController extends BaseController {
|
||||
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<HrmSealReqVo> list(HrmSealReqBo bo, PageQuery pageQuery) {
|
||||
bo.setCreateBy(LoginHelper.getUsername());
|
||||
return service.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
@@ -40,6 +42,11 @@ public class HrmSealReqController extends BaseController {
|
||||
return R.ok(service.queryById(bizId));
|
||||
}
|
||||
|
||||
@GetMapping("/{bizId}/pdfPages")
|
||||
public R<Integer> pdfPages(@PathVariable @NotNull Long bizId) {
|
||||
return R.ok(service.queryPdfPageTotal(bizId));
|
||||
}
|
||||
|
||||
@Log(title = "用印申请", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public R<HrmSealReqVo> add(@Validated @RequestBody HrmSealReqBo bo) {
|
||||
|
||||
@@ -6,10 +6,12 @@ import com.ruoyi.common.core.domain.PageQuery;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.common.helper.LoginHelper;
|
||||
import com.ruoyi.hrm.domain.bo.HrmTravelReqBo;
|
||||
import com.ruoyi.hrm.domain.vo.HrmTravelReqVo;
|
||||
import com.ruoyi.hrm.service.IHrmTravelReqService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@@ -28,9 +30,17 @@ import java.util.List;
|
||||
public class HrmTravelReqController extends BaseController {
|
||||
|
||||
private final IHrmTravelReqService service;
|
||||
private final IHrmTravelReqService hrmTravelReqService;
|
||||
|
||||
@Value("${fad.amap.webkey}")
|
||||
private String amapKey;
|
||||
|
||||
@Value("${fad.amap.securitykey}")
|
||||
private String amapSecurityKey;
|
||||
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<HrmTravelReqVo> list(HrmTravelReqBo bo, PageQuery pageQuery) {
|
||||
bo.setCreateBy(LoginHelper.getUsername());
|
||||
return service.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
@@ -56,9 +66,24 @@ public class HrmTravelReqController extends BaseController {
|
||||
public R<Void> remove(@PathVariable @NotEmpty Long[] bizIds) {
|
||||
return toAjax(service.deleteWithValidByIds(Arrays.asList(bizIds), true));
|
||||
}
|
||||
@PutMapping("/earlyEnd/{bizId}")
|
||||
public R<Void> earlyEnd(@PathVariable Long bizId) {
|
||||
return toAjax(hrmTravelReqService.earlyEnd(bizId));
|
||||
}
|
||||
|
||||
@GetMapping("/amapKey")
|
||||
public R<String> getAmapKey() {
|
||||
return R.ok(amapKey);
|
||||
}
|
||||
|
||||
@GetMapping("/amapSecurityKey")
|
||||
public R<String> getAmapSecurityKey() {
|
||||
return R.ok(amapSecurityKey);
|
||||
}
|
||||
|
||||
@GetMapping("/all")
|
||||
public R<List<HrmTravelReqVo>> all(HrmTravelReqBo bo) {
|
||||
bo.setCreateBy(LoginHelper.getUsername());
|
||||
return R.ok(service.queryList(bo));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,11 @@ import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("hrm_flow_action")
|
||||
public class HrmFlowAction extends BaseEntity implements Serializable {
|
||||
public class HrmFlowAction extends BaseEntity {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.ruoyi.hrm.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 发票条目子表(报销单/拨款单共用)
|
||||
*/
|
||||
@Data
|
||||
@TableName("hrm_invoice_item")
|
||||
public class HrmInvoiceItem {
|
||||
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/** 业务类型 reimburse / appropriation */
|
||||
private String bizType;
|
||||
|
||||
/** 关联业务单ID */
|
||||
private Long bizId;
|
||||
|
||||
/** 来源附件ossId */
|
||||
private Long ossId;
|
||||
|
||||
/** 排序序号 */
|
||||
private Integer sortNo;
|
||||
|
||||
/** OCR识别项目名称 */
|
||||
private String itemName;
|
||||
|
||||
/** 事由说明(用户可编辑) */
|
||||
private String reason;
|
||||
|
||||
/** 金额 */
|
||||
private BigDecimal amount;
|
||||
|
||||
@TableLogic
|
||||
private Integer delFlag;
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 用印申请
|
||||
@@ -15,7 +14,7 @@ import java.io.Serializable;
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("hrm_seal_req")
|
||||
public class HrmSealReq extends BaseEntity implements Serializable {
|
||||
public class HrmSealReq extends BaseEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@@ -29,7 +28,7 @@ public class HrmSealReq extends BaseEntity implements Serializable {
|
||||
/** 项目ID */
|
||||
private Long projectId;
|
||||
|
||||
/** 用印类型(公章/合同章/财务章等) */
|
||||
/** 用印类型(对应印章文件名) */
|
||||
private String sealType;
|
||||
|
||||
/** 用途说明 */
|
||||
|
||||
@@ -32,6 +32,7 @@ public class HrmTravelReq extends BaseEntity {
|
||||
private String bankName;
|
||||
private String bankAccount;
|
||||
private String remark;
|
||||
private Date actualEndTime;
|
||||
@TableLogic
|
||||
private Integer delFlag;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import lombok.EqualsAndHashCode;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 拨款申请 Bo
|
||||
@@ -59,5 +60,8 @@ public class HrmAppropriationReqBo extends BaseEntity {
|
||||
private String remark;
|
||||
|
||||
private Long tplId;
|
||||
|
||||
/** 发票条目列表(前端提交时携带) */
|
||||
private List<HrmInvoiceItemBo> invoiceItems;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.ruoyi.hrm.domain.bo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 发票条目 BO(用于表单提交)
|
||||
*/
|
||||
@Data
|
||||
public class HrmInvoiceItemBo {
|
||||
|
||||
/** 来源附件ossId */
|
||||
private Long ossId;
|
||||
|
||||
/** 排序序号 */
|
||||
private Integer sortNo;
|
||||
|
||||
/** OCR识别项目名称 */
|
||||
private String itemName;
|
||||
|
||||
/** 事由说明 */
|
||||
private String reason;
|
||||
|
||||
/** 金额 */
|
||||
private BigDecimal amount;
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import lombok.EqualsAndHashCode;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@@ -39,5 +40,8 @@ public class HrmReimburseReqBo extends BaseEntity {
|
||||
private String remark;
|
||||
|
||||
private Long tplId;
|
||||
|
||||
/** 发票条目列表(前端提交时携带) */
|
||||
private List<HrmInvoiceItemBo> invoiceItems;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ public class HrmSealReqBo extends BaseEntity {
|
||||
/** 项目ID */
|
||||
private Long projectId;
|
||||
|
||||
/** 用印类型 */
|
||||
/** 用印类型(对应印章文件名) */
|
||||
@NotBlank(message = "用印类型不能为空")
|
||||
private String sealType;
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ import lombok.Data;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import com.ruoyi.hrm.domain.HrmInvoiceItem;
|
||||
|
||||
/**
|
||||
* 拨款申请 VO
|
||||
@@ -108,5 +110,7 @@ public class HrmAppropriationReqVo implements Serializable {
|
||||
|
||||
/** 流程实例ID */
|
||||
private Long instId;
|
||||
|
||||
private List<HrmInvoiceItem> invoiceItems;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.ruoyi.hrm.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
public class HrmFlowActionTimelineVo implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long actionId;
|
||||
private Long taskId;
|
||||
private Long instId;
|
||||
private Long actionUserId;
|
||||
private String actionUserName;
|
||||
private Long assigneeUserId;
|
||||
private String assigneeUserName;
|
||||
private String action;
|
||||
private String actionText;
|
||||
private String remark;
|
||||
private String bizType;
|
||||
private Long bizId;
|
||||
private Long nodeId;
|
||||
private String nodeName;
|
||||
private String taskStatus;
|
||||
private Date createTime;
|
||||
}
|
||||
@@ -34,6 +34,8 @@ public class HrmFlowInstanceVo implements Serializable {
|
||||
private Date endTime;
|
||||
private BigDecimal hours;
|
||||
private String procStatus;
|
||||
private Date actualEndTime;
|
||||
private String statusName;
|
||||
|
||||
private String createBy;
|
||||
private Date createTime;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.ruoyi.hrm.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class HrmFlowTaskDetailVo implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 当前业务对应的待办任务 */
|
||||
private HrmFlowTaskVo currentTask;
|
||||
|
||||
/** 当前业务对应的全部任务历史 */
|
||||
private List<HrmFlowTaskVo> taskHistory;
|
||||
|
||||
/** 当前流程实例状态 */
|
||||
private String flowStatus;
|
||||
|
||||
/** 当前节点ID */
|
||||
private Long currentNodeId;
|
||||
|
||||
/** 当前节点名称 */
|
||||
private String currentNodeName;
|
||||
|
||||
/** 审批是否通过 */
|
||||
private Boolean approved;
|
||||
|
||||
/** 流程动作历史(更细粒度) */
|
||||
private List<HrmFlowActionTimelineVo> actionTimeline;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.ruoyi.hrm.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 单张发票OCR识别结果(返回给前端用于填充发票条目)
|
||||
*/
|
||||
@Data
|
||||
public class HrmInvoiceOcrResultVo {
|
||||
|
||||
/** 发票类型 */
|
||||
private String invoiceType;
|
||||
|
||||
/** 销售方名称 */
|
||||
private String sellerName;
|
||||
|
||||
/** 开票日期 */
|
||||
private String invoiceDate;
|
||||
|
||||
/** 价税合计 */
|
||||
private BigDecimal totalAmount;
|
||||
|
||||
/** 识别出的条目列表 */
|
||||
private List<Item> items;
|
||||
|
||||
@Data
|
||||
public static class Item {
|
||||
/** OCR识别名称 */
|
||||
private String itemName;
|
||||
/** 金额(不含税) */
|
||||
private BigDecimal amount;
|
||||
/** 税率 */
|
||||
private String taxRate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.ruoyi.hrm.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
public class HrmMyApplyVo implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private String bizType;
|
||||
private Long bizId;
|
||||
private Long empId;
|
||||
private String empName;
|
||||
private Long userId;
|
||||
private String nickName;
|
||||
private String title;
|
||||
private String status;
|
||||
private Date createTime;
|
||||
private Date endTime;
|
||||
private Date actualEndTime;
|
||||
private String remark;
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import lombok.Data;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import com.ruoyi.hrm.domain.HrmInvoiceItem;
|
||||
|
||||
@Data
|
||||
public class HrmReimburseReqVo implements Serializable {
|
||||
@@ -95,5 +97,7 @@ public class HrmReimburseReqVo implements Serializable {
|
||||
private Date updateTime;
|
||||
|
||||
private Long instId;
|
||||
|
||||
private List<HrmInvoiceItem> invoiceItems;
|
||||
}
|
||||
|
||||
|
||||
@@ -88,6 +88,9 @@ public class HrmSealReqVo implements Serializable {
|
||||
@Excel(name = "回执附件ID列表")
|
||||
private String receiptFileIds;
|
||||
|
||||
@Excel(name = "PDF页数")
|
||||
private Integer pdfPageTotal;
|
||||
|
||||
@Excel(name = "状态")
|
||||
private String status;
|
||||
|
||||
|
||||
@@ -74,6 +74,8 @@ public class HrmTravelReqVo implements Serializable {
|
||||
private String bankAccount;
|
||||
@Excel(name = "备注")
|
||||
private String remark;
|
||||
|
||||
private Date actualEndTime;
|
||||
private String createBy;
|
||||
private Date createTime;
|
||||
private String updateBy;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.ruoyi.hrm.mapper;
|
||||
|
||||
import com.ruoyi.common.core.mapper.BaseMapperPlus;
|
||||
import com.ruoyi.hrm.domain.HrmInvoiceItem;
|
||||
|
||||
public interface HrmInvoiceItemMapper extends BaseMapperPlus<HrmInvoiceItemMapper, HrmInvoiceItem, HrmInvoiceItem> {
|
||||
}
|
||||
@@ -15,6 +15,8 @@ public interface IHrmEmployeeService {
|
||||
|
||||
List<HrmEmployeeVo> queryList(HrmEmployeeBo bo);
|
||||
|
||||
HrmEmployeeVo queryByUserId(Long userId);
|
||||
|
||||
Boolean insertByBo(HrmEmployeeBo bo);
|
||||
|
||||
Boolean updateByBo(HrmEmployeeBo bo);
|
||||
|
||||
@@ -27,5 +27,9 @@ public interface IHrmFlowCcService {
|
||||
* 标记已读
|
||||
*/
|
||||
Boolean markRead(Long ccId, Long userId);
|
||||
/**
|
||||
* 标记未读
|
||||
*/
|
||||
Boolean markUnread(Long ccId, Long userId);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ package com.ruoyi.hrm.service;
|
||||
|
||||
import com.ruoyi.common.core.domain.PageQuery;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.hrm.domain.HrmFlowTask;
|
||||
import com.ruoyi.hrm.domain.bo.HrmFlowTaskBo;
|
||||
import com.ruoyi.hrm.domain.vo.HrmFlowTaskDetailVo;
|
||||
import com.ruoyi.hrm.domain.vo.HrmFlowTaskVo;
|
||||
|
||||
import java.util.Collection;
|
||||
@@ -51,4 +51,9 @@ public interface IHrmFlowTaskService {
|
||||
* 根据业务类型 + 业务ID 查询当前待办任务(pending),用于详情页自动带出 currentTaskId
|
||||
*/
|
||||
HrmFlowTaskVo queryTodoByBiz(String bizType, Long bizId, Long assigneeUserId);
|
||||
|
||||
/**
|
||||
* 按业务查询详情:当前待办、状态和历史审批记录
|
||||
*/
|
||||
HrmFlowTaskDetailVo queryDetailByBiz(String bizType, Long bizId, Long assigneeUserId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.ruoyi.hrm.service;
|
||||
|
||||
import com.ruoyi.hrm.domain.vo.HrmInvoiceOcrResultVo;
|
||||
|
||||
/**
|
||||
* 发票OCR识别服务(调用Python OCR微服务)
|
||||
*/
|
||||
public interface IHrmInvoiceOcrService {
|
||||
|
||||
/**
|
||||
* 通过ossId识别发票
|
||||
*/
|
||||
HrmInvoiceOcrResultVo recognizeByOssId(Long ossId);
|
||||
|
||||
/**
|
||||
* 检查OCR服务是否存活
|
||||
*/
|
||||
boolean isAlive();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.ruoyi.hrm.service;
|
||||
|
||||
import com.ruoyi.common.core.domain.PageQuery;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.hrm.domain.vo.HrmMyApplyVo;
|
||||
|
||||
public interface IHrmMyApplyService {
|
||||
TableDataInfo<HrmMyApplyVo> queryPageList(String bizType, String status, String keyword, PageQuery pageQuery);
|
||||
}
|
||||
@@ -13,6 +13,8 @@ public interface IHrmSealReqService {
|
||||
|
||||
HrmSealReqVo queryById(Long bizId);
|
||||
|
||||
Integer queryPdfPageTotal(Long bizId);
|
||||
|
||||
TableDataInfo<HrmSealReqVo> queryPageList(HrmSealReqBo bo, PageQuery pageQuery);
|
||||
|
||||
List<HrmSealReqVo> queryList(HrmSealReqBo bo);
|
||||
|
||||
@@ -22,5 +22,7 @@ public interface IHrmTravelReqService {
|
||||
|
||||
Boolean updateByBo(HrmTravelReqBo bo);
|
||||
|
||||
int earlyEnd(Long bizId);
|
||||
|
||||
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
||||
}
|
||||
|
||||
@@ -9,11 +9,14 @@ import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.helper.LoginHelper;
|
||||
import com.ruoyi.hrm.domain.HrmAppropriationReq;
|
||||
import com.ruoyi.hrm.domain.HrmFlowTemplate;
|
||||
import com.ruoyi.hrm.domain.HrmInvoiceItem;
|
||||
import com.ruoyi.hrm.domain.bo.HrmAppropriationReqBo;
|
||||
import com.ruoyi.hrm.domain.bo.HrmFlowStartBo;
|
||||
import com.ruoyi.hrm.domain.bo.HrmInvoiceItemBo;
|
||||
import com.ruoyi.hrm.domain.vo.HrmAppropriationReqVo;
|
||||
import com.ruoyi.hrm.mapper.HrmAppropriationReqMapper;
|
||||
import com.ruoyi.hrm.mapper.HrmFlowTemplateMapper;
|
||||
import com.ruoyi.hrm.mapper.HrmInvoiceItemMapper;
|
||||
import com.ruoyi.hrm.service.IHrmAppropriationReqService;
|
||||
import com.ruoyi.hrm.service.IHrmFlowInstanceService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -33,10 +36,18 @@ public class HrmAppropriationReqServiceImpl implements IHrmAppropriationReqServi
|
||||
private final HrmAppropriationReqMapper baseMapper;
|
||||
private final HrmFlowTemplateMapper flowTemplateMapper;
|
||||
private final IHrmFlowInstanceService flowInstanceService;
|
||||
private final HrmInvoiceItemMapper invoiceItemMapper;
|
||||
|
||||
@Override
|
||||
public HrmAppropriationReqVo queryById(Long bizId) {
|
||||
return baseMapper.selectVoWithProjectById(bizId);
|
||||
HrmAppropriationReqVo vo = baseMapper.selectVoWithProjectById(bizId);
|
||||
if (vo != null) {
|
||||
vo.setInvoiceItems(invoiceItemMapper.selectList(Wrappers.<HrmInvoiceItem>lambdaQuery()
|
||||
.eq(HrmInvoiceItem::getBizType, "appropriation")
|
||||
.eq(HrmInvoiceItem::getBizId, bizId)
|
||||
.orderByAsc(HrmInvoiceItem::getSortNo)));
|
||||
}
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -64,6 +75,12 @@ public class HrmAppropriationReqServiceImpl implements IHrmAppropriationReqServi
|
||||
boolean ok = baseMapper.insert(add) > 0;
|
||||
|
||||
HrmAppropriationReqVo bean = BeanUtil.toBean(add, HrmAppropriationReqVo.class);
|
||||
|
||||
// 保存发票条目
|
||||
if (ok && bo.getInvoiceItems() != null && !bo.getInvoiceItems().isEmpty()) {
|
||||
saveInvoiceItems("appropriation", add.getBizId(), bo.getInvoiceItems());
|
||||
}
|
||||
|
||||
if (ok && "pending".equalsIgnoreCase(add.getStatus())) {
|
||||
// 获取流程启动人ID
|
||||
Long startUserId = LoginHelper.getUserId();
|
||||
@@ -108,7 +125,11 @@ public class HrmAppropriationReqServiceImpl implements IHrmAppropriationReqServi
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean updateByBo(HrmAppropriationReqBo bo) {
|
||||
HrmAppropriationReq update = BeanUtil.toBean(bo, HrmAppropriationReq.class);
|
||||
return baseMapper.updateById(update) > 0;
|
||||
boolean updated = baseMapper.updateById(update) > 0;
|
||||
if (updated && bo.getInvoiceItems() != null) {
|
||||
saveInvoiceItems("appropriation", bo.getBizId(), bo.getInvoiceItems());
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -125,6 +146,24 @@ public class HrmAppropriationReqServiceImpl implements IHrmAppropriationReqServi
|
||||
return baseMapper.updateById(req) > 0;
|
||||
}
|
||||
|
||||
private void saveInvoiceItems(String bizType, Long bizId, List<HrmInvoiceItemBo> boList) {
|
||||
invoiceItemMapper.delete(Wrappers.<HrmInvoiceItem>lambdaQuery()
|
||||
.eq(HrmInvoiceItem::getBizType, bizType)
|
||||
.eq(HrmInvoiceItem::getBizId, bizId));
|
||||
for (int i = 0; i < boList.size(); i++) {
|
||||
HrmInvoiceItemBo bo = boList.get(i);
|
||||
HrmInvoiceItem item = new HrmInvoiceItem();
|
||||
item.setBizType(bizType);
|
||||
item.setBizId(bizId);
|
||||
item.setOssId(bo.getOssId());
|
||||
item.setSortNo(bo.getSortNo() != null ? bo.getSortNo() : i);
|
||||
item.setItemName(bo.getItemName());
|
||||
item.setReason(bo.getReason());
|
||||
item.setAmount(bo.getAmount());
|
||||
invoiceItemMapper.insert(item);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private LambdaQueryWrapper<HrmAppropriationReq> buildQueryWrapper(HrmAppropriationReqBo bo) {
|
||||
LambdaQueryWrapper<HrmAppropriationReq> lqw = Wrappers.lambdaQuery();
|
||||
|
||||
@@ -29,6 +29,11 @@ public class HrmEmployeeServiceImpl implements IHrmEmployeeService {
|
||||
return baseMapper.selectVoById(empId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HrmEmployeeVo queryByUserId(Long userId) {
|
||||
return baseMapper.selectVoOne(Wrappers.<HrmEmployee>lambdaQuery().eq(HrmEmployee::getUserId, userId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableDataInfo<HrmEmployeeVo> queryPageList(HrmEmployeeBo bo, PageQuery pageQuery) {
|
||||
LambdaQueryWrapper<HrmEmployee> lqw = buildQueryWrapper(bo);
|
||||
|
||||
@@ -99,6 +99,23 @@ public class HrmFlowCcServiceImpl implements IHrmFlowCcService {
|
||||
.eq(HrmFlowCc::getDelFlag, 0)
|
||||
) > 0;
|
||||
}
|
||||
/**
|
||||
* 标记未读
|
||||
*/
|
||||
@Override
|
||||
public Boolean markUnread(Long ccId, Long userId) {
|
||||
if (ccId == null || userId == null) {
|
||||
return false;
|
||||
}
|
||||
return baseMapper.update(
|
||||
null,
|
||||
Wrappers.<HrmFlowCc>lambdaUpdate()
|
||||
.set(HrmFlowCc::getReadFlag, 0)
|
||||
.eq(HrmFlowCc::getCcId, ccId)
|
||||
.eq(HrmFlowCc::getCcUserId, userId)
|
||||
.eq(HrmFlowCc::getDelFlag, 0)
|
||||
) > 0;
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<HrmFlowCc> buildQueryWrapper(HrmFlowCcBo bo) {
|
||||
LambdaQueryWrapper<HrmFlowCc> lqw = Wrappers.lambdaQuery();
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.common.core.domain.PageQuery;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.core.service.UserService;
|
||||
import com.ruoyi.common.helper.LoginHelper;
|
||||
import com.ruoyi.hrm.domain.HrmFlowInstance;
|
||||
import com.ruoyi.hrm.domain.HrmFlowNode;
|
||||
import com.ruoyi.hrm.domain.HrmFlowTask;
|
||||
@@ -15,6 +16,7 @@ import com.ruoyi.hrm.domain.bo.HrmFlowStartBo;
|
||||
import com.ruoyi.hrm.domain.bo.HrmFlowInstanceBo;
|
||||
import com.ruoyi.hrm.domain.vo.HrmFlowInstanceVo;
|
||||
import com.ruoyi.hrm.domain.vo.HrmFlowTaskVo;
|
||||
import com.ruoyi.hrm.domain.vo.HrmTravelReqVo;
|
||||
import com.ruoyi.hrm.mapper.*;
|
||||
import com.ruoyi.hrm.service.IHrmFlowInstanceService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -37,6 +39,7 @@ public class HrmFlowInstanceServiceImpl implements IHrmFlowInstanceService {
|
||||
private final FlowAssigneeHelper assigneeHelper;
|
||||
private final UserService userService;
|
||||
private final HrmFlowCcMapper ccMapper;
|
||||
private final HrmTravelReqMapper travelReqMapper;
|
||||
|
||||
@Override
|
||||
public HrmFlowInstanceVo queryById(Long instId) {
|
||||
@@ -75,6 +78,7 @@ public class HrmFlowInstanceServiceImpl implements IHrmFlowInstanceService {
|
||||
task.setNodeId(0L);
|
||||
task.setAssigneeUserId(bo.getManualAssigneeUserId());
|
||||
task.setStatus("pending");
|
||||
task.setRemark("自选审批人一次性审批");
|
||||
// 关键:写入业务关联字段,便于审批中心联查业务数据
|
||||
task.setBizType(bo.getBizType());
|
||||
task.setBizId(bo.getBizId());
|
||||
@@ -121,6 +125,7 @@ public class HrmFlowInstanceServiceImpl implements IHrmFlowInstanceService {
|
||||
task.setNodeId(firstNode.getNodeId());
|
||||
task.setAssigneeUserId(assignees.get(0));
|
||||
task.setStatus("pending");
|
||||
task.setRemark(firstNode.getRemark());
|
||||
// 关键:写入业务关联字段,便于审批中心联查业务数据
|
||||
task.setBizType(bo.getBizType());
|
||||
task.setBizId(bo.getBizId());
|
||||
@@ -140,12 +145,36 @@ public class HrmFlowInstanceServiceImpl implements IHrmFlowInstanceService {
|
||||
public TableDataInfo<HrmFlowInstanceVo> queryMyInstancePageList(HrmFlowInstanceBo bo, PageQuery pageQuery) {
|
||||
// “我的申请”= 当前登录用户发起的流程实例
|
||||
// 这里不信任前端传 startUserId,统一以登录态为准
|
||||
Long userId = com.ruoyi.common.helper.LoginHelper.getUserId();
|
||||
Long userId = LoginHelper.getUserId();
|
||||
LambdaQueryWrapper<HrmFlowInstance> lqw = buildQueryWrapper(bo);
|
||||
lqw.eq(userId != null, HrmFlowInstance::getStartUserId, userId);
|
||||
// 默认按发起时间倒序(如果表里没createTime字段,这里可改成instId倒序)
|
||||
lqw.orderByDesc(HrmFlowInstance::getInstId);
|
||||
Page<HrmFlowInstanceVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
||||
for (HrmFlowInstanceVo record : result.getRecords()) {
|
||||
|
||||
// 写入出差相关的时间,用于在页面中可以提前结束
|
||||
if (record.getBizType().equals("travel")){
|
||||
HrmTravelReqVo hrmTravelReqVo = travelReqMapper.selectVoById(record.getBizId());
|
||||
record.setActualEndTime(hrmTravelReqVo.getActualEndTime());
|
||||
}
|
||||
}
|
||||
for (HrmFlowInstanceVo vo : result.getRecords()) {
|
||||
if ("travel".equals(vo.getBizType())) {
|
||||
HrmTravelReqVo travel = travelReqMapper.selectVoById(vo.getBizId());
|
||||
if (travel != null) {
|
||||
vo.setActualEndTime(travel.getActualEndTime());
|
||||
// 只有流程已完成,才替换显示文字
|
||||
if ("complete".equals(vo.getStatus())) {
|
||||
if (travel.getActualEndTime() == null) {
|
||||
vo.setStatusName("出差中");
|
||||
} else {
|
||||
vo.setStatusName("已结束");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,11 +12,15 @@ import com.ruoyi.common.helper.LoginHelper;
|
||||
import com.ruoyi.hrm.domain.*;
|
||||
import com.ruoyi.hrm.domain.bo.HrmFlowTaskBo;
|
||||
import com.ruoyi.hrm.domain.bo.HrmSealStampBo;
|
||||
import com.ruoyi.hrm.domain.vo.HrmFlowActionTimelineVo;
|
||||
import com.ruoyi.hrm.domain.vo.HrmFlowTaskDetailVo;
|
||||
import com.ruoyi.hrm.domain.vo.HrmFlowTaskVo;
|
||||
import com.ruoyi.hrm.domain.vo.HrmEmployeeVo;
|
||||
import com.ruoyi.hrm.mapper.*;
|
||||
import com.ruoyi.hrm.service.IHrmFlowTaskService;
|
||||
import com.ruoyi.hrm.service.IHrmSealReqService;
|
||||
import com.ruoyi.hrm.service.IHrmFlowCcService;
|
||||
import com.ruoyi.hrm.service.IHrmEmployeeService;
|
||||
import com.ruoyi.hrm.domain.bo.HrmFlowCcBo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -38,7 +42,6 @@ public class HrmFlowTaskServiceImpl implements IHrmFlowTaskService {
|
||||
private final FlowAssigneeHelper assigneeHelper;
|
||||
private final BizStatusSyncHelper bizStatusSyncHelper;
|
||||
|
||||
private final HrmFlowTaskMapper hrmFlowTaskMapper;
|
||||
// 注入五个业务Mapper
|
||||
private final HrmLeaveReqMapper leaveReqMapper;
|
||||
private final HrmTravelReqMapper travelReqMapper;
|
||||
@@ -47,6 +50,7 @@ public class HrmFlowTaskServiceImpl implements IHrmFlowTaskService {
|
||||
private final HrmAppropriationReqMapper appropriationReqMapper;
|
||||
private final ObjectMapper objectMapper; // Spring Boot 默认提供
|
||||
private final UserService userService;
|
||||
private final IHrmEmployeeService employeeService;
|
||||
|
||||
@Override
|
||||
public HrmFlowTaskVo queryById(Long taskId) {
|
||||
@@ -90,19 +94,39 @@ public class HrmFlowTaskServiceImpl implements IHrmFlowTaskService {
|
||||
if (bizIds.isEmpty()) return;
|
||||
switch (bizType) {
|
||||
case "leave":
|
||||
leaveReqMapper.selectBatchIds(bizIds).forEach(d -> bizDataMap.put("leave_" + d.getBizId(), d));
|
||||
leaveReqMapper.selectBatchIds(bizIds).forEach(d -> {
|
||||
Map<String, Object> dataMap = objectMapper.convertValue(d, Map.class);
|
||||
fillEmpName(dataMap, d.getEmpId());
|
||||
bizDataMap.put("leave_" + d.getBizId(), dataMap);
|
||||
});
|
||||
break;
|
||||
case "travel":
|
||||
travelReqMapper.selectBatchIds(bizIds).forEach(d -> bizDataMap.put("travel_" + d.getBizId(), d));
|
||||
travelReqMapper.selectBatchIds(bizIds).forEach(d -> {
|
||||
Map<String, Object> dataMap = objectMapper.convertValue(d, Map.class);
|
||||
fillEmpName(dataMap, d.getEmpId());
|
||||
bizDataMap.put("travel_" + d.getBizId(), dataMap);
|
||||
});
|
||||
break;
|
||||
case "seal":
|
||||
sealReqMapper.selectBatchIds(bizIds).forEach(d -> bizDataMap.put("seal_" + d.getBizId(), d));
|
||||
sealReqMapper.selectBatchIds(bizIds).forEach(d -> {
|
||||
Map<String, Object> dataMap = objectMapper.convertValue(d, Map.class);
|
||||
fillEmpName(dataMap, d.getEmpId());
|
||||
bizDataMap.put("seal_" + d.getBizId(), dataMap);
|
||||
});
|
||||
break;
|
||||
case "reimburse":
|
||||
reimburseReqMapper.selectBatchIds(bizIds).forEach(d -> bizDataMap.put("reimburse_" + d.getBizId(), d));
|
||||
reimburseReqMapper.selectBatchIds(bizIds).forEach(d -> {
|
||||
Map<String, Object> dataMap = objectMapper.convertValue(d, Map.class);
|
||||
fillEmpName(dataMap, d.getEmpId());
|
||||
bizDataMap.put("reimburse_" + d.getBizId(), dataMap);
|
||||
});
|
||||
break;
|
||||
case "appropriation":
|
||||
appropriationReqMapper.selectBatchIds(bizIds).forEach(d -> bizDataMap.put("appropriation_" + d.getBizId(), d));
|
||||
appropriationReqMapper.selectBatchIds(bizIds).forEach(d -> {
|
||||
Map<String, Object> dataMap = objectMapper.convertValue(d, Map.class);
|
||||
fillEmpName(dataMap, d.getEmpId());
|
||||
bizDataMap.put("appropriation_" + d.getBizId(), dataMap);
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
@@ -112,13 +136,20 @@ public class HrmFlowTaskServiceImpl implements IHrmFlowTaskService {
|
||||
String key = task.getBizType() + "_" + task.getBizId();
|
||||
Object data = bizDataMap.get(key);
|
||||
if (data != null) {
|
||||
// 将实体对象转换为 Map<String, Object>,方便前端使用
|
||||
Map<String, Object> dataMap = objectMapper.convertValue(data, Map.class);
|
||||
task.setBizData(dataMap);
|
||||
task.setBizData((Map<String, Object>) data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void fillEmpName(Map<String, Object> dataMap, Long empId) {
|
||||
if (empId != null) {
|
||||
HrmEmployeeVo emp = employeeService.queryById(empId);
|
||||
if (emp != null && emp.getEmpName() != null) {
|
||||
dataMap.put("empName", emp.getEmpName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@@ -155,12 +186,13 @@ public class HrmFlowTaskServiceImpl implements IHrmFlowTaskService {
|
||||
if (inst == null) {
|
||||
return false;
|
||||
}
|
||||
Long operatorUserId = actionUserId != null ? actionUserId : LoginHelper.getUserId();
|
||||
// 无模板一次性审批(tplId=0 或 nodeId=0):直接结束流程
|
||||
if (inst.getTplId() != null && inst.getTplId() == 0L) {
|
||||
// 记录动作
|
||||
saveAction(taskId, inst.getInstId(), "approve", remark, actionUserId,task.getBizType(), task.getBizId());
|
||||
saveAction(taskId, inst.getInstId(), task.getBizType(), task.getBizId(), task.getAssigneeUserId(), "approve", remark, operatorUserId);
|
||||
if (stampBo != null) {
|
||||
saveAction(taskId, inst.getInstId(), "stamp", "盖章", actionUserId,task.getBizType(), task.getBizId());
|
||||
saveAction(taskId, inst.getInstId(), task.getBizType(), task.getBizId(), task.getAssigneeUserId(), "stamp", "盖章", operatorUserId);
|
||||
}
|
||||
task.setStatus("approved");
|
||||
baseMapper.updateById(task);
|
||||
@@ -172,7 +204,7 @@ public class HrmFlowTaskServiceImpl implements IHrmFlowTaskService {
|
||||
sealReqService.updateStatus(inst.getBizId(), "approved");
|
||||
if (stampBo != null) {
|
||||
// 盖章动作也写入流转历史
|
||||
saveAction(taskId, inst.getInstId(), "stamp", "盖章", actionUserId,task.getBizType(), task.getBizId());
|
||||
saveAction(taskId, inst.getInstId(), task.getBizType(), task.getBizId(), task.getAssigneeUserId(), "stamp", "盖章", operatorUserId);
|
||||
sealReqService.stampWithJava(inst.getBizId(), stampBo);
|
||||
}
|
||||
}
|
||||
@@ -185,7 +217,7 @@ public class HrmFlowTaskServiceImpl implements IHrmFlowTaskService {
|
||||
return false;
|
||||
}
|
||||
// 记录动作
|
||||
saveAction(taskId, inst.getInstId(), "approve", remark, actionUserId,task.getBizType(),task.getBizId());
|
||||
saveAction(taskId, inst.getInstId(), task.getBizType(), task.getBizId(), task.getAssigneeUserId(), "approve", remark, operatorUserId);
|
||||
// 完成当前任务
|
||||
task.setStatus("approved");
|
||||
baseMapper.updateById(task);
|
||||
@@ -269,7 +301,8 @@ public class HrmFlowTaskServiceImpl implements IHrmFlowTaskService {
|
||||
if (inst == null) {
|
||||
return false;
|
||||
}
|
||||
saveAction(taskId, inst.getInstId(), "reject", remark, actionUserId,task.getBizType(),task.getBizId());
|
||||
Long operatorUserId = actionUserId != null ? actionUserId : LoginHelper.getUserId();
|
||||
saveAction(taskId, inst.getInstId(), task.getBizType(), task.getBizId(), task.getAssigneeUserId(), "reject", remark, operatorUserId);
|
||||
task.setStatus("rejected");
|
||||
baseMapper.updateById(task);
|
||||
inst.setStatus("rejected");
|
||||
@@ -293,7 +326,8 @@ public class HrmFlowTaskServiceImpl implements IHrmFlowTaskService {
|
||||
if (inst == null) {
|
||||
return false;
|
||||
}
|
||||
saveAction(taskId, inst.getInstId(), "withdraw", remark, actionUserId, task.getBizType(), task.getBizId());
|
||||
Long operatorUserId = actionUserId != null ? actionUserId : LoginHelper.getUserId();
|
||||
saveAction(taskId, inst.getInstId(), task.getBizType(), task.getBizId(), task.getAssigneeUserId(), "withdraw", remark, operatorUserId);
|
||||
task.setStatus("withdraw");
|
||||
baseMapper.updateById(task);
|
||||
// 无模板一次性审批:撤回后业务回到 pending,并重新生成一个待办(仍然只允许一次审批)
|
||||
@@ -330,13 +364,13 @@ public class HrmFlowTaskServiceImpl implements IHrmFlowTaskService {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void saveAction(Long taskId, Long instId, String action, String remark, Long userId, String bizType, Long bizId) {
|
||||
private void saveAction(Long taskId, Long instId, String bizType, Long bizId, Long assigneeUserId, String action, String remark, Long actionUserId) {
|
||||
HrmFlowAction log = new HrmFlowAction();
|
||||
log.setTaskId(taskId);
|
||||
log.setInstId(instId);
|
||||
log.setAction(action);
|
||||
log.setRemark(remark);
|
||||
log.setActionUserId(userId);
|
||||
log.setActionUserId(actionUserId);
|
||||
log.setCreateTime(new Date());
|
||||
log.setBizType(bizType);
|
||||
log.setBizId(bizId);
|
||||
@@ -358,7 +392,8 @@ public class HrmFlowTaskServiceImpl implements IHrmFlowTaskService {
|
||||
return false;
|
||||
}
|
||||
// 记录动作
|
||||
saveAction(taskId, inst.getInstId(), "transfer", remark, actionUserId, task.getBizType(), task.getBizId());
|
||||
Long operatorUserId = actionUserId != null ? actionUserId : LoginHelper.getUserId();
|
||||
saveAction(taskId, inst.getInstId(), task.getBizType(), task.getBizId(), newAssigneeUserId, "transfer", remark, operatorUserId);
|
||||
|
||||
// 更新办理人
|
||||
HrmFlowTask u = new HrmFlowTask();
|
||||
@@ -369,7 +404,6 @@ public class HrmFlowTaskServiceImpl implements IHrmFlowTaskService {
|
||||
|
||||
@Override
|
||||
public HrmFlowTaskVo queryTodoByBiz(String bizType, Long bizId, Long assigneeUserId) {
|
||||
// 只取"待办 pending"的一条(理论上同一 biz 同一时刻最多一条待办)
|
||||
LambdaQueryWrapper<HrmFlowTask> lqw = Wrappers.<HrmFlowTask>lambdaQuery()
|
||||
.eq(bizType != null, HrmFlowTask::getBizType, bizType)
|
||||
.eq(bizId != null, HrmFlowTask::getBizId, bizId)
|
||||
@@ -378,12 +412,118 @@ public class HrmFlowTaskServiceImpl implements IHrmFlowTaskService {
|
||||
.orderByDesc(HrmFlowTask::getTaskId)
|
||||
.last("limit 1");
|
||||
HrmFlowTaskVo hrmFlowTaskVo = baseMapper.selectVoOne(lqw);
|
||||
if (hrmFlowTaskVo != null) {
|
||||
if (hrmFlowTaskVo != null && hrmFlowTaskVo.getAssigneeUserId() != null) {
|
||||
hrmFlowTaskVo.setAssigneeNickName(userService.selectNickNameById(hrmFlowTaskVo.getAssigneeUserId()));
|
||||
}
|
||||
return hrmFlowTaskVo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HrmFlowTaskDetailVo queryDetailByBiz(String bizType, Long bizId, Long assigneeUserId) {
|
||||
HrmFlowTaskDetailVo result = new HrmFlowTaskDetailVo();
|
||||
HrmFlowTaskVo currentTask = queryTodoByBiz(bizType, bizId, assigneeUserId);
|
||||
result.setCurrentTask(currentTask);
|
||||
|
||||
LambdaQueryWrapper<HrmFlowTask> historyQ = Wrappers.<HrmFlowTask>lambdaQuery()
|
||||
.eq(bizType != null, HrmFlowTask::getBizType, bizType)
|
||||
.eq(bizId != null, HrmFlowTask::getBizId, bizId)
|
||||
.orderByAsc(HrmFlowTask::getCreateTime);
|
||||
List<HrmFlowTaskVo> histories = baseMapper.selectVoList(historyQ);
|
||||
if (histories != null) {
|
||||
histories.forEach(task -> {
|
||||
if (task.getAssigneeUserId() != null) {
|
||||
task.setAssigneeNickName(userService.selectNickNameById(task.getAssigneeUserId()));
|
||||
}
|
||||
});
|
||||
}
|
||||
result.setTaskHistory(histories == null ? Collections.emptyList() : histories);
|
||||
|
||||
HrmFlowInstance inst = instanceMapper.selectOne(Wrappers.<HrmFlowInstance>lambdaQuery()
|
||||
.eq(bizType != null, HrmFlowInstance::getBizType, bizType)
|
||||
.eq(bizId != null, HrmFlowInstance::getBizId, bizId)
|
||||
.orderByDesc(HrmFlowInstance::getInstId)
|
||||
.last("limit 1"));
|
||||
if (inst != null) {
|
||||
result.setFlowStatus(inst.getStatus());
|
||||
result.setCurrentNodeId(inst.getCurrentNodeId());
|
||||
HrmFlowNode node = inst.getCurrentNodeId() == null ? null : nodeMapper.selectById(inst.getCurrentNodeId());
|
||||
if (node != null) {
|
||||
result.setCurrentNodeName(node.getRemark());
|
||||
}
|
||||
result.setApproved(Boolean.valueOf("approved".equalsIgnoreCase(inst.getStatus())));
|
||||
}
|
||||
|
||||
result.setActionTimeline(buildActionTimeline(bizType, bizId));
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<HrmFlowActionTimelineVo> buildActionTimeline(String bizType, Long bizId) {
|
||||
LambdaQueryWrapper<HrmFlowAction> actionQ = Wrappers.<HrmFlowAction>lambdaQuery()
|
||||
.eq(bizType != null, HrmFlowAction::getBizType, bizType)
|
||||
.eq(bizId != null, HrmFlowAction::getBizId, bizId)
|
||||
.orderByAsc(HrmFlowAction::getCreateTime);
|
||||
List<HrmFlowAction> actions = actionMapper.selectList(actionQ);
|
||||
if (actions == null || actions.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
Map<Long, HrmFlowTask> taskMap = new HashMap<>();
|
||||
List<Long> taskIds = actions.stream().map(HrmFlowAction::getTaskId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
|
||||
if (!taskIds.isEmpty()) {
|
||||
baseMapper.selectBatchIds(taskIds).forEach(task -> taskMap.put(task.getTaskId(), task));
|
||||
}
|
||||
|
||||
List<HrmFlowActionTimelineVo> timelines = new ArrayList<HrmFlowActionTimelineVo>();
|
||||
for (HrmFlowAction action : actions) {
|
||||
HrmFlowActionTimelineVo vo = new HrmFlowActionTimelineVo();
|
||||
vo.setActionId(action.getActionId());
|
||||
vo.setTaskId(action.getTaskId());
|
||||
vo.setInstId(action.getInstId());
|
||||
vo.setActionUserId(action.getActionUserId());
|
||||
vo.setActionUserName(action.getActionUserId() == null ? null : userService.selectNickNameById(action.getActionUserId()));
|
||||
vo.setAction(action.getAction());
|
||||
vo.setActionText(actionText(action.getAction()));
|
||||
vo.setRemark(action.getRemark());
|
||||
vo.setBizType(action.getBizType());
|
||||
vo.setBizId(action.getBizId());
|
||||
HrmFlowTask task = taskMap.get(action.getTaskId());
|
||||
if (task != null) {
|
||||
vo.setNodeId(task.getNodeId());
|
||||
vo.setTaskStatus(task.getStatus());
|
||||
HrmFlowNode node = task.getNodeId() == null ? null : nodeMapper.selectById(task.getNodeId());
|
||||
if (node != null) {
|
||||
vo.setNodeName(node.getRemark());
|
||||
}
|
||||
}
|
||||
vo.setCreateTime(action.getCreateTime());
|
||||
timelines.add(vo);
|
||||
}
|
||||
return timelines;
|
||||
}
|
||||
|
||||
private String actionText(String action) {
|
||||
if (action == null) {
|
||||
return "-";
|
||||
}
|
||||
String lower = action.toLowerCase();
|
||||
if ("approve".equals(lower)) {
|
||||
return "通过";
|
||||
}
|
||||
if ("reject".equals(lower)) {
|
||||
return "驳回";
|
||||
}
|
||||
if ("withdraw".equals(lower)) {
|
||||
return "撤回";
|
||||
}
|
||||
if ("transfer".equals(lower)) {
|
||||
return "转办";
|
||||
}
|
||||
if ("stamp".equals(lower)) {
|
||||
return "盖章";
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<HrmFlowTask> buildQueryWrapper(HrmFlowTaskBo bo) {
|
||||
LambdaQueryWrapper<HrmFlowTask> lqw = Wrappers.lambdaQuery();
|
||||
lqw.eq(bo.getTaskId() != null, HrmFlowTask::getTaskId, bo.getTaskId());
|
||||
@@ -416,6 +556,11 @@ public class HrmFlowTaskServiceImpl implements IHrmFlowTaskService {
|
||||
|
||||
if (result.getRecords() != null && !result.getRecords().isEmpty()) {
|
||||
fillBizData(result.getRecords());
|
||||
result.getRecords().forEach(task -> {
|
||||
if (task.getAssigneeUserId() != null) {
|
||||
task.setAssigneeNickName(userService.selectNickNameById(task.getAssigneeUserId()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return TableDataInfo.build(result);
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
package com.ruoyi.hrm.service.impl;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.hrm.domain.vo.HrmInvoiceOcrResultVo;
|
||||
import com.ruoyi.hrm.service.IHrmInvoiceOcrService;
|
||||
import com.ruoyi.oss.factory.OssFactory;
|
||||
import com.ruoyi.system.mapper.SysOssMapper;
|
||||
import com.ruoyi.system.domain.vo.SysOssVo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 发票OCR识别服务实现(调用Python OCR微服务)
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class HrmInvoiceOcrServiceImpl implements IHrmInvoiceOcrService {
|
||||
|
||||
|
||||
|
||||
@Value("${fad.ocr.url}")
|
||||
String ocrUrl;
|
||||
|
||||
@Value("${fad.ocr.api-key}")
|
||||
String apiKey;
|
||||
|
||||
private final SysOssMapper sysOssMapper;
|
||||
|
||||
@Override
|
||||
public HrmInvoiceOcrResultVo recognizeByOssId(Long ossId) {
|
||||
SysOssVo oss = sysOssMapper.selectVoById(ossId);
|
||||
if (oss == null) {
|
||||
throw new ServiceException("附件不存在: " + ossId);
|
||||
}
|
||||
|
||||
byte[] fileBytes;
|
||||
try (InputStream in = OssFactory.instance().getObjectContent(oss.getUrl())) {
|
||||
fileBytes = IoUtil.readBytes(in);
|
||||
} catch (Exception e) {
|
||||
throw new ServiceException("读取附件失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
String fileName = StringUtils.defaultIfBlank(oss.getOriginalName(), oss.getFileName());
|
||||
return callOcrService(fileBytes, fileName, oss.getFileSuffix());
|
||||
}
|
||||
|
||||
private HrmInvoiceOcrResultVo callOcrService(byte[] fileBytes, String fileName, String fileSuffix) {
|
||||
|
||||
if (StringUtils.isBlank(ocrUrl)) {
|
||||
throw new ServiceException("OCR服务地址未配置,请检查 fad.ocr.url");
|
||||
}
|
||||
|
||||
// 推断 content-type
|
||||
String suffix = StringUtils.defaultIfBlank(fileSuffix, "").toLowerCase().replace(".", "");
|
||||
String contentType;
|
||||
switch (suffix) {
|
||||
case "pdf":
|
||||
contentType = "application/pdf";
|
||||
break;
|
||||
case "png":
|
||||
contentType = "image/png";
|
||||
break;
|
||||
case "jpg":
|
||||
case "jpeg":
|
||||
contentType = "image/jpeg";
|
||||
break;
|
||||
default:
|
||||
contentType = "application/octet-stream";
|
||||
}
|
||||
|
||||
// 构建 multipart 请求
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
|
||||
if (StringUtils.isNotBlank(apiKey)) {
|
||||
headers.set("X-API-Key", apiKey);
|
||||
}
|
||||
|
||||
final String finalContentType = contentType;
|
||||
final String finalFileName = StringUtils.defaultIfBlank(fileName, "invoice" + "." + suffix);
|
||||
ByteArrayResource fileResource = new ByteArrayResource(fileBytes) {
|
||||
@Override
|
||||
public String getFilename() {
|
||||
return finalFileName;
|
||||
}
|
||||
};
|
||||
|
||||
HttpHeaders fileHeaders = new HttpHeaders();
|
||||
fileHeaders.setContentType(MediaType.parseMediaType(finalContentType));
|
||||
HttpEntity<ByteArrayResource> filePart = new HttpEntity<>(fileResource, fileHeaders);
|
||||
|
||||
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
||||
body.add("file", filePart);
|
||||
|
||||
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
|
||||
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
ResponseEntity<String> response;
|
||||
try {
|
||||
response = restTemplate.postForEntity(ocrUrl + "/v1/invoice/ocr", requestEntity, String.class);
|
||||
} catch (Exception e) {
|
||||
log.error("[OCR] 调用OCR服务失败 url={} error={}", ocrUrl, e.getMessage());
|
||||
throw new ServiceException("OCR服务调用失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
if (!response.getStatusCode().is2xxSuccessful() || response.getBody() == null) {
|
||||
throw new ServiceException("OCR服务返回异常: " + response.getStatusCode());
|
||||
}
|
||||
|
||||
return parseOcrResponse(response.getBody());
|
||||
}
|
||||
|
||||
private HrmInvoiceOcrResultVo parseOcrResponse(String responseBody) {
|
||||
HrmInvoiceOcrResultVo result = new HrmInvoiceOcrResultVo();
|
||||
try {
|
||||
JSONObject root = JSON.parseObject(responseBody);
|
||||
JSONObject data = root.getJSONObject("data");
|
||||
if (data == null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result.setInvoiceType(getFieldValue(data, "invoice_type"));
|
||||
result.setSellerName(getFieldValue(data, "seller_name"));
|
||||
result.setInvoiceDate(getFieldValue(data, "invoice_date"));
|
||||
|
||||
String totalAmountStr = getFieldValue(data, "amount_with_tax");
|
||||
if (StringUtils.isBlank(totalAmountStr)) {
|
||||
totalAmountStr = getFieldValue(data, "total_amount");
|
||||
}
|
||||
result.setTotalAmount(parseBigDecimal(totalAmountStr));
|
||||
|
||||
// 解析明细行
|
||||
List<HrmInvoiceOcrResultVo.Item> items = new ArrayList<>();
|
||||
JSONArray lineItems = data.getJSONArray("line_items");
|
||||
if (lineItems != null) {
|
||||
for (int i = 0; i < lineItems.size(); i++) {
|
||||
JSONObject li = lineItems.getJSONObject(i);
|
||||
if (li == null) continue;
|
||||
String name = getStringOrFieldValue(li, "project_name");
|
||||
if (StringUtils.isBlank(name)) continue;
|
||||
|
||||
HrmInvoiceOcrResultVo.Item item = new HrmInvoiceOcrResultVo.Item();
|
||||
item.setItemName(name);
|
||||
// 行项目金额取价税合计:税前金额 + 税额
|
||||
BigDecimal preAmt = parseBigDecimal(getStringOrFieldValue(li, "amount"));
|
||||
BigDecimal taxAmt = parseBigDecimal(getStringOrFieldValue(li, "tax_amount"));
|
||||
BigDecimal withTax = null;
|
||||
if (preAmt != null || taxAmt != null) {
|
||||
withTax = (preAmt != null ? preAmt : BigDecimal.ZERO)
|
||||
.add(taxAmt != null ? taxAmt : BigDecimal.ZERO);
|
||||
}
|
||||
item.setAmount(withTax != null ? withTax : preAmt);
|
||||
item.setTaxRate(getStringOrFieldValue(li, "tax_rate"));
|
||||
items.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有明细行但有总金额,生成一个汇总条目
|
||||
if (items.isEmpty() && result.getTotalAmount() != null) {
|
||||
HrmInvoiceOcrResultVo.Item item = new HrmInvoiceOcrResultVo.Item();
|
||||
String seller = StringUtils.defaultIfBlank(result.getSellerName(), "发票款项");
|
||||
item.setItemName(seller);
|
||||
item.setAmount(result.getTotalAmount());
|
||||
items.add(item);
|
||||
}
|
||||
|
||||
result.setItems(items);
|
||||
} catch (Exception e) {
|
||||
log.warn("[OCR] 解析OCR响应失败: {}", e.getMessage());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 从 {value: "...", confidence: 0.9} 结构取值,或直接取字符串 */
|
||||
private String getFieldValue(JSONObject obj, String key) {
|
||||
Object val = obj.get(key);
|
||||
if (val == null) return null;
|
||||
if (val instanceof JSONObject) {
|
||||
return ((JSONObject) val).getString("value");
|
||||
}
|
||||
return val.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAlive() {
|
||||
|
||||
if (StringUtils.isBlank(ocrUrl)) return false;
|
||||
try {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
ResponseEntity<String> resp = restTemplate.getForEntity(ocrUrl + "/health", String.class);
|
||||
return resp.getStatusCode().is2xxSuccessful();
|
||||
} catch (Exception e) {
|
||||
log.warn("[OCR] 健康检查失败: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private String getStringOrFieldValue(JSONObject obj, String key) {
|
||||
Object val = obj.get(key);
|
||||
if (val == null) return null;
|
||||
if (val instanceof JSONObject) {
|
||||
return ((JSONObject) val).getString("value");
|
||||
}
|
||||
return val.toString();
|
||||
}
|
||||
|
||||
private BigDecimal parseBigDecimal(String raw) {
|
||||
if (StringUtils.isBlank(raw)) return null;
|
||||
try {
|
||||
return new BigDecimal(raw.replace(",", "").replace("¥", "").replace("¥", "").trim());
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,11 +8,14 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.common.core.domain.PageQuery;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.hrm.domain.HrmFlowTemplate;
|
||||
import com.ruoyi.hrm.domain.HrmInvoiceItem;
|
||||
import com.ruoyi.hrm.domain.HrmReimburseReq;
|
||||
import com.ruoyi.hrm.domain.bo.HrmFlowStartBo;
|
||||
import com.ruoyi.hrm.domain.bo.HrmInvoiceItemBo;
|
||||
import com.ruoyi.hrm.domain.bo.HrmReimburseReqBo;
|
||||
import com.ruoyi.hrm.domain.vo.HrmReimburseReqVo;
|
||||
import com.ruoyi.hrm.mapper.HrmFlowTemplateMapper;
|
||||
import com.ruoyi.hrm.mapper.HrmInvoiceItemMapper;
|
||||
import com.ruoyi.hrm.mapper.HrmReimburseReqMapper;
|
||||
import com.ruoyi.hrm.service.IHrmFlowInstanceService;
|
||||
import com.ruoyi.hrm.service.IHrmReimburseReqService;
|
||||
@@ -30,10 +33,18 @@ public class HrmReimburseReqServiceImpl implements IHrmReimburseReqService {
|
||||
private final HrmReimburseReqMapper baseMapper;
|
||||
private final HrmFlowTemplateMapper flowTemplateMapper;
|
||||
private final IHrmFlowInstanceService flowInstanceService;
|
||||
private final HrmInvoiceItemMapper invoiceItemMapper;
|
||||
|
||||
@Override
|
||||
public HrmReimburseReqVo queryById(Long bizId) {
|
||||
return baseMapper.selectVoWithProjectById(bizId);
|
||||
HrmReimburseReqVo vo = baseMapper.selectVoWithProjectById(bizId);
|
||||
if (vo != null) {
|
||||
vo.setInvoiceItems(invoiceItemMapper.selectList(Wrappers.<HrmInvoiceItem>lambdaQuery()
|
||||
.eq(HrmInvoiceItem::getBizType, "reimburse")
|
||||
.eq(HrmInvoiceItem::getBizId, bizId)
|
||||
.orderByAsc(HrmInvoiceItem::getSortNo)));
|
||||
}
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -61,6 +72,12 @@ public class HrmReimburseReqServiceImpl implements IHrmReimburseReqService {
|
||||
boolean ok = baseMapper.insert(add) > 0;
|
||||
|
||||
HrmReimburseReqVo bean = BeanUtil.toBean(add, HrmReimburseReqVo.class);
|
||||
|
||||
// 保存发票条目
|
||||
if (ok && bo.getInvoiceItems() != null && !bo.getInvoiceItems().isEmpty()) {
|
||||
saveInvoiceItems("reimburse", add.getBizId(), bo.getInvoiceItems());
|
||||
}
|
||||
|
||||
if (ok && "pending".equalsIgnoreCase(add.getStatus())) {
|
||||
Long startUserId = LoginHelper.getUserId();
|
||||
|
||||
@@ -105,7 +122,11 @@ public class HrmReimburseReqServiceImpl implements IHrmReimburseReqService {
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean updateByBo(HrmReimburseReqBo bo) {
|
||||
HrmReimburseReq update = BeanUtil.toBean(bo, HrmReimburseReq.class);
|
||||
return baseMapper.updateById(update) > 0;
|
||||
boolean updated = baseMapper.updateById(update) > 0;
|
||||
if (updated && bo.getInvoiceItems() != null) {
|
||||
saveInvoiceItems("reimburse", bo.getBizId(), bo.getInvoiceItems());
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -124,6 +145,25 @@ public class HrmReimburseReqServiceImpl implements IHrmReimburseReqService {
|
||||
return lqw;
|
||||
}
|
||||
|
||||
private void saveInvoiceItems(String bizType, Long bizId, List<HrmInvoiceItemBo> boList) {
|
||||
// 先清除旧数据,再插入新数据(更新场景兼容)
|
||||
invoiceItemMapper.delete(Wrappers.<HrmInvoiceItem>lambdaQuery()
|
||||
.eq(HrmInvoiceItem::getBizType, bizType)
|
||||
.eq(HrmInvoiceItem::getBizId, bizId));
|
||||
for (int i = 0; i < boList.size(); i++) {
|
||||
HrmInvoiceItemBo bo = boList.get(i);
|
||||
HrmInvoiceItem item = new HrmInvoiceItem();
|
||||
item.setBizType(bizType);
|
||||
item.setBizId(bizId);
|
||||
item.setOssId(bo.getOssId());
|
||||
item.setSortNo(bo.getSortNo() != null ? bo.getSortNo() : i);
|
||||
item.setItemName(bo.getItemName());
|
||||
item.setReason(bo.getReason());
|
||||
item.setAmount(bo.getAmount());
|
||||
invoiceItemMapper.insert(item);
|
||||
}
|
||||
}
|
||||
|
||||
private String defaultStatus(String status) {
|
||||
return status == null ? "draft" : status;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import com.ruoyi.hrm.service.IHrmSealReqService;
|
||||
import com.ruoyi.oss.core.OssClient;
|
||||
import com.ruoyi.oss.entity.UploadResult;
|
||||
import com.ruoyi.oss.factory.OssFactory;
|
||||
import com.ruoyi.system.domain.vo.SysOssVo;
|
||||
import com.ruoyi.system.mapper.SysOssMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
@@ -28,7 +30,6 @@ import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -48,10 +49,59 @@ public class HrmSealReqServiceImpl implements IHrmSealReqService {
|
||||
private final HrmSealReqMapper baseMapper;
|
||||
private final StampProperties stampProperties;
|
||||
private final IHrmFlowInstanceService flowInstanceService;
|
||||
private final SysOssMapper sysOssMapper;
|
||||
|
||||
@Override
|
||||
public HrmSealReqVo queryById(Long bizId) {
|
||||
return baseMapper.selectVoWithProjectById(bizId);
|
||||
HrmSealReqVo vo = baseMapper.selectVoWithProjectById(bizId);
|
||||
if (vo != null) {
|
||||
vo.setPdfPageTotal(queryPdfPageTotal(bizId));
|
||||
}
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer queryPdfPageTotal(Long bizId) {
|
||||
HrmSealReqVo vo = baseMapper.selectVoWithProjectById(bizId);
|
||||
String applyFileIds = vo != null ? vo.getApplyFileIds() : null;
|
||||
if (applyFileIds == null || applyFileIds.trim().isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
String firstFileId = applyFileIds.split(",")[0].trim();
|
||||
if (firstFileId.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
String fileUrl = resolveSealPdfUrl(firstFileId);
|
||||
if (fileUrl == null || fileUrl.trim().isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
try (InputStream inputStream = getObject(fileUrl); PDDocument document = PDDocument.load(inputStream)) {
|
||||
return document.getNumberOfPages();
|
||||
} catch (Exception e) {
|
||||
log.warn("查询用印PDF页数失败 bizId={}, fileId={}, fileUrl={}", bizId, firstFileId, fileUrl, e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private String resolveObjectKey(String fileRef) {
|
||||
if (fileRef == null) {
|
||||
return null;
|
||||
}
|
||||
String ref = fileRef.trim();
|
||||
if (ref.isEmpty()) {
|
||||
return ref;
|
||||
}
|
||||
if (ref.startsWith("http://") || ref.startsWith("https://")) {
|
||||
int idx = ref.indexOf("/files/");
|
||||
if (idx >= 0) {
|
||||
return ref.substring(idx + "/files/".length());
|
||||
}
|
||||
idx = ref.indexOf("/files%2F");
|
||||
if (idx >= 0) {
|
||||
return ref.substring(idx + "/files%2F".length());
|
||||
}
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -269,6 +319,17 @@ public class HrmSealReqServiceImpl implements IHrmSealReqService {
|
||||
}
|
||||
}
|
||||
|
||||
private String resolveSealPdfUrl(String ossIdText) {
|
||||
try {
|
||||
Long ossId = Long.valueOf(ossIdText);
|
||||
SysOssVo sysOss = sysOssMapper.selectVoById(ossId);
|
||||
return sysOss != null ? sysOss.getUrl() : null;
|
||||
} catch (Exception e) {
|
||||
log.warn("解析用印PDF文件地址失败 ossIdText={}", ossIdText, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private InputStream getObject(String url) {
|
||||
OssClient storage = OssFactory.instance();
|
||||
return storage.getObjectContent(url);
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Date;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
@@ -128,4 +129,36 @@ public class HrmTravelReqServiceImpl implements IHrmTravelReqService {
|
||||
private String defaultStatus(String status) {
|
||||
return status == null ? "draft" : status;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int earlyEnd(Long bizId) {
|
||||
HrmTravelReq travelReq = baseMapper.selectById(bizId);
|
||||
if (travelReq == null) {
|
||||
throw new RuntimeException("出差申请不存在");
|
||||
}
|
||||
|
||||
String status = travelReq.getStatus();
|
||||
if (!"approved".equals(status) && !"in_progress".equals(status)) {
|
||||
throw new RuntimeException("当前状态不能提前结束,只有已通过或进行中的出差才能提前结束");
|
||||
}
|
||||
|
||||
// 3. 检查是否已经提前结束过
|
||||
if (travelReq.getActualEndTime() != null) {
|
||||
throw new RuntimeException("该出差已经提前结束过了");
|
||||
}
|
||||
|
||||
// 4. 更新实际结束时间为当前时间
|
||||
travelReq.setActualEndTime(new Date());
|
||||
// 5. 可选:更新状态为已完成
|
||||
travelReq.setStatus("completed");
|
||||
|
||||
// 6. 执行更新
|
||||
int result = baseMapper.updateById(travelReq);
|
||||
if (result <= 0) {
|
||||
throw new RuntimeException("提前结束失败");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,12 +51,13 @@
|
||||
<sql id="selectVo">
|
||||
SELECT
|
||||
e.emp_id, e.user_id, e.emp_no, e.emp_name, e.gender, e.mobile, e.email, e.id_no,
|
||||
e.hire_date, e.employment_type, e.status, e.dept_id, e.post_id, e.remark,
|
||||
e.hire_date, e.employment_type, e.status, su.dept_id, e.post_id, e.remark,
|
||||
e.create_by, e.create_time, e.update_by, e.update_time,
|
||||
d.dept_name,
|
||||
p.post_name
|
||||
FROM hrm_employee e
|
||||
LEFT JOIN sys_dept d ON e.dept_id = d.dept_id AND d.del_flag = '0'
|
||||
left join sys_user su on su.user_id = e.user_id
|
||||
LEFT JOIN sys_dept d ON su.dept_id = d.dept_id AND d.del_flag = 0
|
||||
LEFT JOIN sys_post p ON e.post_id = p.post_id
|
||||
WHERE e.del_flag = 0
|
||||
</sql>
|
||||
|
||||
@@ -170,6 +170,8 @@ security:
|
||||
- /oa/attendanceRecord/**
|
||||
- /oa/oaWarehouse/**
|
||||
- /oa/oaWarehouseMaster/**
|
||||
# 高德逆地理(经纬度转城市等,供前端/H5 调用)
|
||||
- /oa/amap/**
|
||||
|
||||
# MyBatisPlus配置
|
||||
# https://baomidou.com/config/
|
||||
@@ -317,3 +319,17 @@ flowable:
|
||||
check-process-definitions: false
|
||||
# 关闭历史任务定时任务job
|
||||
async-history-executor-activate: false
|
||||
|
||||
fad:
|
||||
amap:
|
||||
# 留作后端接口调用(服务 API)的 Key
|
||||
key: 978ae5bc551f57d172d3e397af5a6f67
|
||||
# 新增的前端 Web 端使用的 Key 和安全密钥
|
||||
webKey: 34bf20d1db5b183558b9bb85d6eed783
|
||||
securityKey: 6f9171724396deb5f8c42ef256b3cbc5
|
||||
ocr:
|
||||
# 发票OCR服务地址(ai-ocr Python服务)
|
||||
url: http://127.0.0.1:8810
|
||||
# OCR服务 API Key
|
||||
api-key: change-me-debug-key
|
||||
|
||||
|
||||
@@ -2,13 +2,20 @@ package com.ruoyi.framework.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@Configuration
|
||||
public class RestTemplateConfig {
|
||||
|
||||
private static final int CONNECT_TIMEOUT_MS = 5_000;
|
||||
private static final int READ_TIMEOUT_MS = 15_000;
|
||||
|
||||
@Bean
|
||||
public RestTemplate restTemplate() {
|
||||
return new RestTemplate();
|
||||
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
|
||||
factory.setConnectTimeout(CONNECT_TIMEOUT_MS);
|
||||
factory.setReadTimeout(READ_TIMEOUT_MS);
|
||||
return new RestTemplate(factory);
|
||||
}
|
||||
}
|
||||
@@ -68,11 +68,20 @@
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
||||
<groupId>com.xuxueli</groupId>
|
||||
<artifactId>xxl-job-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>fad-hrm</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.pdfbox</groupId>
|
||||
<artifactId>pdfbox</artifactId>
|
||||
<version>2.0.29</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.ruoyi.oa.controller;
|
||||
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.oa.domain.vo.AmapCityNameVo;
|
||||
import com.ruoyi.oa.service.IOaAmapGeocodeService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
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.DecimalMax;
|
||||
import javax.validation.constraints.DecimalMin;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* 高德地图:经纬度逆地理编码(城市名等)
|
||||
*/
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/oa/amap")
|
||||
public class OaAmapController extends BaseController {
|
||||
|
||||
private final IOaAmapGeocodeService oaAmapGeocodeService;
|
||||
|
||||
/**
|
||||
* 根据经纬度获取城市名称(高德逆地理编码)
|
||||
*/
|
||||
@GetMapping("/city")
|
||||
public R<AmapCityNameVo> cityByLocation(
|
||||
@NotNull(message = "经度不能为空")
|
||||
@DecimalMin(value = "-180.0", message = "经度范围无效")
|
||||
@DecimalMax(value = "180.0", message = "经度范围无效")
|
||||
@RequestParam Double longitude,
|
||||
@NotNull(message = "纬度不能为空")
|
||||
@DecimalMin(value = "-90.0", message = "纬度范围无效")
|
||||
@DecimalMax(value = "90.0", message = "纬度范围无效")
|
||||
@RequestParam Double latitude
|
||||
) {
|
||||
AmapCityNameVo vo = oaAmapGeocodeService.reverseGeocodeCity(longitude, latitude);
|
||||
return R.ok(vo);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.ruoyi.oa.controller;
|
||||
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.annotation.RepeatSubmit;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.PageQuery;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.core.validate.AddGroup;
|
||||
import com.ruoyi.common.core.validate.EditGroup;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.oa.domain.bo.OaCityBo;
|
||||
import com.ruoyi.oa.domain.vo.OaCityVo;
|
||||
import com.ruoyi.oa.service.IOaCityService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/oa/city")
|
||||
@Validated
|
||||
public class OaCityController extends BaseController {
|
||||
|
||||
private final IOaCityService cityService;
|
||||
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<OaCityVo> list(OaCityBo bo, PageQuery pageQuery) {
|
||||
return cityService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
@GetMapping("/{cityId}")
|
||||
public R<OaCityVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long cityId) {
|
||||
return R.ok(cityService.queryById(cityId));
|
||||
}
|
||||
|
||||
@Log(title = "城市管理", businessType = BusinessType.INSERT)
|
||||
@RepeatSubmit()
|
||||
@PostMapping
|
||||
public R<Void> add(@Validated(AddGroup.class) @RequestBody OaCityBo bo) {
|
||||
return toAjax(cityService.insertByBo(bo));
|
||||
}
|
||||
|
||||
@Log(title = "城市管理", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PutMapping
|
||||
public R<Void> edit(@Validated(EditGroup.class) @RequestBody OaCityBo bo) {
|
||||
return toAjax(cityService.updateByBo(bo));
|
||||
}
|
||||
|
||||
@Log(title = "城市管理", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{cityIds}")
|
||||
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] cityIds) {
|
||||
return toAjax(cityService.deleteWithValidByIds(Arrays.asList(cityIds), true));
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||
import com.ruoyi.oa.domain.bo.OaProjectReportBo;
|
||||
import com.ruoyi.oa.domain.vo.OaProjectReportVo;
|
||||
import com.ruoyi.oa.domain.vo.OaProjectTravelCompareVo;
|
||||
import com.ruoyi.oa.domain.vo.ProjectReportCardVo;
|
||||
import com.ruoyi.oa.domain.vo.ProjectReportPieVo;
|
||||
import com.ruoyi.oa.domain.vo.ProjectReportTrendVo;
|
||||
@@ -51,6 +52,15 @@ public class OaProjectReportController extends BaseController {
|
||||
return iOaProjectReportService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
@GetMapping("/travel-compare")
|
||||
public TableDataInfo<OaProjectTravelCompareVo> travelCompare(@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate start,
|
||||
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate end,
|
||||
@RequestParam(required = false) String nickName,
|
||||
@RequestParam(required = false) String workPlace,
|
||||
PageQuery pageQuery) {
|
||||
return iOaProjectReportService.getTravelCompareList(start, end, nickName, workPlace, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询项目报工列表
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.ruoyi.oa.controller;
|
||||
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.oa.domain.bo.OaProjectScheduleBo;
|
||||
import com.ruoyi.oa.domain.bo.OaProjectScheduleStepBo;
|
||||
import com.ruoyi.oa.domain.vo.OaProjectScheduleStepVo;
|
||||
import com.ruoyi.oa.domain.vo.OaProjectScheduleVo;
|
||||
import com.ruoyi.oa.domain.vo.OaScheduleListVo;
|
||||
import com.ruoyi.oa.service.IOaProjectScheduleService;
|
||||
import com.ruoyi.oa.service.IOaProjectScheduleStepService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 项目进度(主表 + 子表聚合)
|
||||
*
|
||||
* 该控制器用于满足综合看板接口规范:
|
||||
* - GET /oa/schedule/list?projectId=xx
|
||||
*
|
||||
* 复用现有服务:
|
||||
* - /oa/projectSchedule(主表)
|
||||
* - /oa/projectScheduleStep(子表)
|
||||
*/
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/oa/schedule")
|
||||
public class OaScheduleController extends BaseController {
|
||||
|
||||
private final IOaProjectScheduleService projectScheduleService;
|
||||
private final IOaProjectScheduleStepService projectScheduleStepService;
|
||||
|
||||
/**
|
||||
* 获取进度主表 + 子表数据
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public R<List<OaScheduleListVo>> list(@RequestParam("projectId") Long projectId) {
|
||||
OaProjectScheduleBo scheduleBo = new OaProjectScheduleBo();
|
||||
scheduleBo.setProjectId(projectId);
|
||||
List<OaProjectScheduleVo> schedules = projectScheduleService.queryList(scheduleBo);
|
||||
|
||||
List<OaScheduleListVo> result = new ArrayList<>();
|
||||
for (OaProjectScheduleVo schedule : schedules) {
|
||||
List<OaProjectScheduleStepVo> steps = projectScheduleStepService.selectProjectScheduleStepList(schedule.getScheduleId());
|
||||
|
||||
OaScheduleListVo vo = new OaScheduleListVo();
|
||||
vo.setSchedule(schedule);
|
||||
vo.setStepList(steps);
|
||||
result.add(vo);
|
||||
}
|
||||
return R.ok(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,17 @@ public class SysOaProjectController extends BaseController {
|
||||
return R.ok(iSysOaProjectService.getMaxCode(prefix));
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目综合看板聚合数据(项目 + 任务 + 进度主表 + 步骤)
|
||||
* 必须声明在 /{projectId} 之前,避免部分环境下通配路径优先匹配导致 404。
|
||||
* 菜单权限请在 sys_menu 中配置后使用全局路由守卫或接口级注解单独加回。
|
||||
*/
|
||||
@GetMapping("/dashboard/{projectId}")
|
||||
public R<OaProjectDashboardVo> dashboard(@NotNull(message = "主键不能为空") @PathVariable Long projectId) {
|
||||
OaProjectDashboardVo data = iSysOaProjectService.getProjectDashboard(projectId);
|
||||
return data == null ? R.fail("项目不存在") : R.ok(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取项目管理详细信息
|
||||
*
|
||||
|
||||
49
ruoyi-oa/src/main/java/com/ruoyi/oa/domain/OaCity.java
Normal file
49
ruoyi-oa/src/main/java/com/ruoyi/oa/domain/OaCity.java
Normal file
@@ -0,0 +1,49 @@
|
||||
package com.ruoyi.oa.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 城市管理对象 oa_city
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("oa_city")
|
||||
public class OaCity extends BaseEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(value = "city_id")
|
||||
private Long cityId;
|
||||
|
||||
/** 国家 */
|
||||
private String countryName;
|
||||
|
||||
/** 城市 */
|
||||
private String cityName;
|
||||
|
||||
/**
|
||||
* 城市编码
|
||||
*/
|
||||
private String cityCode;
|
||||
|
||||
/**
|
||||
* 所属省份
|
||||
*/
|
||||
private String provinceName;
|
||||
|
||||
|
||||
/** 状态 1正常 0禁用 */
|
||||
private Long status;
|
||||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
|
||||
@TableLogic
|
||||
private Integer delFlag;
|
||||
}
|
||||
29
ruoyi-oa/src/main/java/com/ruoyi/oa/domain/bo/OaCityBo.java
Normal file
29
ruoyi-oa/src/main/java/com/ruoyi/oa/domain/bo/OaCityBo.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package com.ruoyi.oa.domain.bo;
|
||||
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* 城市管理业务对象 oa_city
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class OaCityBo extends BaseEntity {
|
||||
|
||||
private Long cityId;
|
||||
|
||||
@NotBlank(message = "国家不能为空")
|
||||
private String countryName;
|
||||
|
||||
@NotBlank(message = "城市不能为空")
|
||||
private String cityName;
|
||||
|
||||
private Long status;
|
||||
private String cityCode;
|
||||
private String provinceName;
|
||||
private String remark;
|
||||
}
|
||||
@@ -201,6 +201,17 @@ public class SysOaProjectBo extends BaseEntity {
|
||||
private Long customerId;
|
||||
//客户名称
|
||||
private String customerName;
|
||||
|
||||
/**
|
||||
* 综合模糊查询(名称 / 编号 / 代号 任一匹配),与 projectName、projectNum、projectCode 互斥:有值时优先使用本字段
|
||||
*/
|
||||
private String keyword;
|
||||
|
||||
/**
|
||||
* 为 true 时在列表结果中附带各项目进度步骤统计(综合看板等,不参与 SQL 条件)
|
||||
*/
|
||||
private Boolean scheduleStats;
|
||||
|
||||
//是否置顶
|
||||
private Integer isTop;
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.ruoyi.oa.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 项目维度进度步骤汇总(综合看板左侧列表等)
|
||||
*/
|
||||
@Data
|
||||
public class ProjectScheduleStepStatsDto {
|
||||
|
||||
private Long projectId;
|
||||
|
||||
private Long totalNodes;
|
||||
|
||||
private Long completedNodes;
|
||||
|
||||
private Long pendingAcceptNodes;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.ruoyi.oa.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 高德逆地理编码:城市名称(及可选行政区信息,便于展示/调试)
|
||||
*/
|
||||
@Data
|
||||
public class AmapCityNameVo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 城市名(直辖市、省直辖等场景可能为省名或区名,与高德 addressComponent 一致) */
|
||||
private String cityName;
|
||||
|
||||
/** 省 */
|
||||
private String province;
|
||||
|
||||
/** 区/县 */
|
||||
private String district;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.ruoyi.oa.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 发票识别明细行
|
||||
*/
|
||||
@Data
|
||||
public class InvoiceOcrItemVo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 货物或应税劳务、服务名称 */
|
||||
private String itemName;
|
||||
|
||||
/** 金额 */
|
||||
private BigDecimal amount;
|
||||
|
||||
/** 税率 */
|
||||
private String taxRate;
|
||||
|
||||
/** 税额 */
|
||||
private BigDecimal taxAmount;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.ruoyi.oa.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 发票识别结果
|
||||
*/
|
||||
@Data
|
||||
public class InvoiceOcrResultVo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 文件名 */
|
||||
private String fileName;
|
||||
|
||||
/** 发票类型 */
|
||||
private String invoiceType;
|
||||
|
||||
/** 发票代码 */
|
||||
private String invoiceCode;
|
||||
|
||||
/** 发票号码 */
|
||||
private String invoiceNumber;
|
||||
|
||||
/** 开票日期 */
|
||||
private String invoiceDate;
|
||||
|
||||
/** 购买方名称 */
|
||||
private String buyerName;
|
||||
|
||||
/** 销售方名称 */
|
||||
private String sellerName;
|
||||
|
||||
/** 不含税金额 */
|
||||
private BigDecimal amountWithoutTax;
|
||||
|
||||
/** 税额 */
|
||||
private BigDecimal taxAmount;
|
||||
|
||||
/** 价税合计 */
|
||||
private BigDecimal totalAmount;
|
||||
|
||||
/** 发票内容摘要 */
|
||||
private String contentSummary;
|
||||
|
||||
/** 提取到的原始文本 */
|
||||
private String rawText;
|
||||
|
||||
/** 明细 */
|
||||
private List<InvoiceOcrItemVo> items = new ArrayList<>();
|
||||
}
|
||||
27
ruoyi-oa/src/main/java/com/ruoyi/oa/domain/vo/OaCityVo.java
Normal file
27
ruoyi-oa/src/main/java/com/ruoyi/oa/domain/vo/OaCityVo.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package com.ruoyi.oa.domain.vo;
|
||||
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 城市管理视图对象 oa_city
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class OaCityVo extends BaseEntity {
|
||||
|
||||
private Long cityId;
|
||||
|
||||
private String countryName;
|
||||
|
||||
private String cityName;
|
||||
|
||||
private Long status;
|
||||
|
||||
private String remark;
|
||||
/** 城市编码 */
|
||||
private String cityCode;
|
||||
/** 所属省份 */
|
||||
private String provinceName;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.ruoyi.oa.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 项目综合看板聚合数据
|
||||
*/
|
||||
@Data
|
||||
public class OaProjectDashboardVo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private SysOaProjectVo project;
|
||||
|
||||
private List<SysOaTaskVo> tasks;
|
||||
|
||||
private List<OaProjectScheduleVo> schedules;
|
||||
|
||||
private List<OaProjectScheduleStepVo> steps;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.ruoyi.oa.domain.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 报工出差比对结果
|
||||
*/
|
||||
@Data
|
||||
public class OaProjectTravelCompareVo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 用户昵称 */
|
||||
private String nickName;
|
||||
|
||||
/** 比对日期(一天) */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate compareDate;
|
||||
|
||||
/** 是否出差 */
|
||||
private Long isTrip;
|
||||
|
||||
/** 是否出差(中文) */
|
||||
private String trip;
|
||||
|
||||
/** 报工地点 */
|
||||
private String workPlace;
|
||||
|
||||
/** 实际出差地点 */
|
||||
private String travelPlace;
|
||||
|
||||
/** 机器比对结果 */
|
||||
private String compareResult;
|
||||
|
||||
/** 是否通过 */
|
||||
private Boolean pass;
|
||||
|
||||
/** 出差记录ID */
|
||||
private Long travelBizId;
|
||||
|
||||
/** 报工ID */
|
||||
private Long reportId;
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import com.ruoyi.common.annotation.ExcelDictFormat;
|
||||
import com.ruoyi.common.convert.ExcelDictConvert;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
import java.util.Date;
|
||||
|
||||
@@ -19,7 +20,7 @@ import java.util.Date;
|
||||
*/
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
public class OaRequirementsVo {
|
||||
public class OaRequirementsVo extends BaseEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.ruoyi.oa.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 项目进度列表(主表 + 子表)
|
||||
*
|
||||
* 对应表:
|
||||
* - oa_project_schedule(主表)
|
||||
* - oa_project_schedule_step(子表)
|
||||
*
|
||||
* 该 VO 用于接口:GET /oa/schedule/list
|
||||
*/
|
||||
@Data
|
||||
public class OaScheduleListVo implements Serializable {
|
||||
|
||||
/** 进度主表 */
|
||||
private OaProjectScheduleVo schedule;
|
||||
|
||||
/** 进度子表步骤 */
|
||||
private List<OaProjectScheduleStepVo> stepList;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.Date;
|
||||
|
||||
import com.alibaba.excel.annotation.format.DateTimeFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.alibaba.excel.annotation.ExcelIgnore;
|
||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import com.ruoyi.common.annotation.ExcelDictFormat;
|
||||
@@ -14,7 +15,6 @@ import com.ruoyi.system.domain.SysOss;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -284,4 +284,27 @@ public class SysOaProjectVo {
|
||||
private Long processCardCount;
|
||||
|
||||
private Long deliveryOrderCount;
|
||||
|
||||
/** 列表 SQL 聚合:任务/进度总览 */
|
||||
@ExcelIgnore
|
||||
private Long taskFinishCount;
|
||||
|
||||
@ExcelIgnore
|
||||
private Long taskTotalCount;
|
||||
|
||||
@ExcelIgnore
|
||||
private Long scheduleTotalCount;
|
||||
|
||||
@ExcelIgnore
|
||||
private Long scheduleFinishCount;
|
||||
|
||||
/** 进度步骤汇总(列表请求 scheduleStats=true 时由服务层填充) */
|
||||
@ExcelIgnore
|
||||
private Long scheduleStepTotal;
|
||||
|
||||
@ExcelIgnore
|
||||
private Long scheduleStepCompleted;
|
||||
|
||||
@ExcelIgnore
|
||||
private Long scheduleStepPendingAccept;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.ruoyi.oa.domain.vo;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
@@ -218,11 +216,28 @@ public class SysOaTaskVo {
|
||||
//部门名称
|
||||
private String deptName;
|
||||
|
||||
//关联项目进度id
|
||||
/** 关联进度子表主键 oa_project_schedule_step.track_id */
|
||||
private Long trackId;
|
||||
|
||||
//联查的节点信息
|
||||
private String tabNode;
|
||||
private String firstLevelNode;
|
||||
private String secondLevelNode;
|
||||
|
||||
/** 进度步骤名称 oa_project_schedule_step.step_name(综合看板联表) */
|
||||
private String scheduleStepName;
|
||||
|
||||
/** 进度步骤状态 0未开始 1进行中 2完成 3暂停 */
|
||||
private Long scheduleStatus;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private Date schedulePlanStart;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private Date schedulePlanEnd;
|
||||
|
||||
/** 进度负责人 oa_project_schedule_step.header */
|
||||
private String scheduleHeader;
|
||||
}
|
||||
|
||||
11
ruoyi-oa/src/main/java/com/ruoyi/oa/mapper/OaCityMapper.java
Normal file
11
ruoyi-oa/src/main/java/com/ruoyi/oa/mapper/OaCityMapper.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package com.ruoyi.oa.mapper;
|
||||
|
||||
import com.ruoyi.common.core.mapper.BaseMapperPlus;
|
||||
import com.ruoyi.oa.domain.OaCity;
|
||||
import com.ruoyi.oa.domain.vo.OaCityVo;
|
||||
|
||||
/**
|
||||
* 城市管理 Mapper
|
||||
*/
|
||||
public interface OaCityMapper extends BaseMapperPlus<OaCityMapper, OaCity, OaCityVo> {
|
||||
}
|
||||
@@ -52,5 +52,11 @@ public interface OaProjectReportMapper extends BaseMapperPlus<OaProjectReportMap
|
||||
|
||||
List<OaProjectReportVo> getClearList(@Param("start") LocalDate start, @Param("end") LocalDate end);
|
||||
|
||||
Page<OaProjectReportVo> selectTravelCompareReportPage(@Param("page") Page<OaProjectReportVo> page,
|
||||
@Param("start") LocalDate start,
|
||||
@Param("end") LocalDate end,
|
||||
@Param("nickName") String nickName,
|
||||
@Param("workPlace") String workPlace);
|
||||
|
||||
List<OaProjectReportVo> getSummaryData(@Param("start") LocalDate start, @Param("end") LocalDate end);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.ruoyi.oa.mapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.oa.domain.OaProjectScheduleStep;
|
||||
import com.ruoyi.oa.domain.dto.ProjectScheduleStepStatsDto;
|
||||
import com.ruoyi.oa.domain.vo.OaProjectScheduleStepVo;
|
||||
import com.ruoyi.common.core.mapper.BaseMapperPlus;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
@@ -31,6 +32,11 @@ public interface OaProjectScheduleStepMapper extends BaseMapperPlus<OaProjectSch
|
||||
|
||||
void saveBatch(List<OaProjectScheduleStep> entities);
|
||||
|
||||
/**
|
||||
* 按 schedule_id 查询步骤列表(综合看板使用)
|
||||
*/
|
||||
List<OaProjectScheduleStepVo> selectProjectScheduleStepList(@Param("scheduleId") Long scheduleId);
|
||||
|
||||
/**
|
||||
* 根据 schedule_id 批量删除步骤记录
|
||||
* @param scheduleIds 主表的 schedule_id 集合
|
||||
@@ -39,4 +45,9 @@ public interface OaProjectScheduleStepMapper extends BaseMapperPlus<OaProjectSch
|
||||
int deleteByScheduleIds(@Param("scheduleIds") Collection<Long> scheduleIds);
|
||||
|
||||
Page<OaProjectScheduleStepVo> selectVoPageNew(Page<Object> build,@Param(Constants.WRAPPER) QueryWrapper<OaProjectScheduleStep> lqw);
|
||||
|
||||
/**
|
||||
* 按项目汇总进度步骤:总数、已完成(2)、待验收(1)
|
||||
*/
|
||||
List<ProjectScheduleStepStatsDto> selectStepStatsGroupByProjectId(@Param("projectIds") Collection<Long> projectIds);
|
||||
}
|
||||
|
||||
@@ -46,6 +46,11 @@ public interface SysOaTaskMapper extends BaseMapperPlus<SysOaTaskMapper, SysOaTa
|
||||
|
||||
List<SysOaTaskVo> listDocumentProject(Long projectId);
|
||||
|
||||
/**
|
||||
* 综合看板:某项目下任务(一行一条任务)
|
||||
*/
|
||||
List<SysOaTaskVo> selectDashboardTasksByProjectId(@Param("projectId") Long projectId);
|
||||
|
||||
/**
|
||||
* 新增:自定义查询任务列表(Plus版)
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.ruoyi.oa.service;
|
||||
|
||||
import com.ruoyi.oa.domain.vo.AmapCityNameVo;
|
||||
|
||||
/**
|
||||
* 高德地图逆地理编码(经纬度 → 城市等)
|
||||
*/
|
||||
public interface IOaAmapGeocodeService {
|
||||
|
||||
/**
|
||||
* 根据经纬度解析城市名称等信息
|
||||
*
|
||||
* @param longitude 经度
|
||||
* @param latitude 纬度
|
||||
* @return 非 null;解析失败时 cityName 等可能为空
|
||||
*/
|
||||
AmapCityNameVo reverseGeocodeCity(Double longitude, Double latitude);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.ruoyi.oa.service;
|
||||
|
||||
import com.ruoyi.common.core.domain.PageQuery;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.oa.domain.bo.OaCityBo;
|
||||
import com.ruoyi.oa.domain.vo.OaCityVo;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public interface IOaCityService {
|
||||
|
||||
OaCityVo queryById(Long cityId);
|
||||
|
||||
TableDataInfo<OaCityVo> queryPageList(OaCityBo bo, PageQuery pageQuery);
|
||||
|
||||
List<OaCityVo> queryList(OaCityBo bo);
|
||||
|
||||
Boolean insertByBo(OaCityBo bo);
|
||||
|
||||
Boolean updateByBo(OaCityBo bo);
|
||||
|
||||
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
||||
}
|
||||
@@ -1,78 +1,48 @@
|
||||
package com.ruoyi.oa.service;
|
||||
|
||||
import com.ruoyi.oa.domain.vo.OaProjectReportVo;
|
||||
import com.ruoyi.oa.domain.bo.OaProjectReportBo;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.core.domain.PageQuery;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.oa.domain.bo.OaProjectReportBo;
|
||||
import com.ruoyi.oa.domain.vo.OaProjectReportVo;
|
||||
import com.ruoyi.oa.domain.vo.OaProjectTravelCompareVo;
|
||||
import com.ruoyi.oa.domain.vo.ProjectReportCardVo;
|
||||
import com.ruoyi.oa.domain.vo.ProjectReportPieVo;
|
||||
import com.ruoyi.oa.domain.vo.ProjectReportTrendVo;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 项目报工Service接口
|
||||
*
|
||||
* @author hdka
|
||||
* @date 2025-06-16
|
||||
*/
|
||||
public interface IOaProjectReportService {
|
||||
|
||||
/**
|
||||
* 查询项目报工
|
||||
*/
|
||||
OaProjectReportVo queryById(Long reportId);
|
||||
|
||||
/**
|
||||
* 查询项目报工列表
|
||||
*/
|
||||
TableDataInfo<OaProjectReportVo> queryPageList(OaProjectReportBo bo, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询项目报工列表
|
||||
*/
|
||||
List<OaProjectReportVo> queryList(OaProjectReportBo bo);
|
||||
|
||||
/**
|
||||
* 新增项目报工
|
||||
*/
|
||||
Boolean insertByBo(OaProjectReportBo bo);
|
||||
|
||||
/**
|
||||
* 修改项目报工
|
||||
*/
|
||||
Boolean updateByBo(OaProjectReportBo bo);
|
||||
|
||||
/**
|
||||
* 校验并批量删除项目报工信息
|
||||
*/
|
||||
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
||||
|
||||
/**
|
||||
* 报工数据看板
|
||||
* @return
|
||||
*/
|
||||
ProjectReportCardVo getCardData();
|
||||
|
||||
public List<ProjectReportTrendVo> getTrend(LocalDate start, LocalDate end);
|
||||
|
||||
public List<ProjectReportPieVo> getDistribution(LocalDate start, LocalDate end);
|
||||
|
||||
|
||||
List<OaProjectReportVo> getRankData(LocalDate start, LocalDate end);
|
||||
|
||||
List<OaProjectReportVo> getProjects(LocalDate start, LocalDate end);
|
||||
|
||||
List<OaProjectReportVo> clearList(LocalDate start, LocalDate end);
|
||||
|
||||
OaProjectReportVo queryById(Long reportId);
|
||||
|
||||
Boolean insertByBo(OaProjectReportBo bo);
|
||||
|
||||
Boolean updateByBo(OaProjectReportBo bo);
|
||||
|
||||
Boolean deleteWithValidByIds(List<Long> ids, Boolean isValid);
|
||||
|
||||
List<OaProjectReportVo> getRankData(LocalDate start, LocalDate end);
|
||||
|
||||
ProjectReportCardVo getCardData();
|
||||
|
||||
List<ProjectReportTrendVo> getTrend(LocalDate start, LocalDate end);
|
||||
|
||||
List<ProjectReportPieVo> getDistribution(LocalDate start, LocalDate end);
|
||||
|
||||
List<OaProjectReportVo> getProjects(LocalDate start, LocalDate end);
|
||||
|
||||
List<OaProjectReportVo> getSummaryData(LocalDate start, LocalDate end);
|
||||
|
||||
/**
|
||||
* 查询当前登录用户今日的报工记录
|
||||
*/
|
||||
OaProjectReportVo getTodayReportByCurrentUser();
|
||||
|
||||
Boolean insertReportSupplement(OaProjectReportBo bo);
|
||||
|
||||
TableDataInfo<OaProjectTravelCompareVo> getTravelCompareList(LocalDate start, LocalDate end, String nickName, String workPlace, PageQuery pageQuery);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,11 @@ public interface IOaProjectScheduleStepService{
|
||||
*/
|
||||
List<OaProjectScheduleStepVo> queryList(OaProjectScheduleStepBo bo);
|
||||
|
||||
/**
|
||||
* 按 scheduleId 查询进度步骤列表(综合看板聚合接口使用)
|
||||
*/
|
||||
List<OaProjectScheduleStepVo> selectProjectScheduleStepList(Long scheduleId);
|
||||
|
||||
/**
|
||||
* 新增项目进度步骤跟踪
|
||||
*/
|
||||
|
||||
@@ -103,4 +103,9 @@ public interface ISysOaProjectService {
|
||||
Boolean postponeProject(SysOaProject bo);
|
||||
|
||||
SysOaProjectVo getMaxCode(String prefix);
|
||||
|
||||
/**
|
||||
* 综合看板:项目详情 + 任务 + 进度主表 + 进度步骤(一次返回)
|
||||
*/
|
||||
OaProjectDashboardVo getProjectDashboard(Long projectId);
|
||||
}
|
||||
|
||||
@@ -109,6 +109,11 @@ public interface ISysOaTaskService {
|
||||
* @return
|
||||
*/
|
||||
List<SysOaTaskVo> listDocumentProject(Long projectId);
|
||||
|
||||
/**
|
||||
* 综合看板任务列表(完整字段,无 task_item 重复行)
|
||||
*/
|
||||
List<SysOaTaskVo> listDashboardTasks(Long projectId);
|
||||
/**
|
||||
* 新增:自定义查询任务列表(Plus版)
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
package com.ruoyi.oa.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.ruoyi.oa.domain.vo.AmapCityNameVo;
|
||||
import com.ruoyi.oa.service.IOaAmapGeocodeService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* 高德逆地理编码:restapi.amap.com/v3/geocode/regeo
|
||||
* <p>
|
||||
* 配置项:{@code fad.amap.key}(与 {@code application.yml} 中一致)
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class OaAmapGeocodeServiceImpl implements IOaAmapGeocodeService {
|
||||
|
||||
private static final String AMAP_REGEO_URL = "https://restapi.amap.com/v3/geocode/regeo";
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
|
||||
@Value("${fad.amap.key:}")
|
||||
private String amapKey;
|
||||
|
||||
@Override
|
||||
public AmapCityNameVo reverseGeocodeCity(Double longitude, Double latitude) {
|
||||
AmapCityNameVo vo = new AmapCityNameVo();
|
||||
if (longitude == null || latitude == null) {
|
||||
return vo;
|
||||
}
|
||||
if (!StringUtils.hasText(amapKey)) {
|
||||
log.warn("fad.amap.key 未配置,无法调用高德逆地理编码");
|
||||
return vo;
|
||||
}
|
||||
|
||||
try {
|
||||
// 高德要求:location = 经度,纬度
|
||||
String location = longitude + "," + latitude;
|
||||
String url = UriComponentsBuilder.fromHttpUrl(AMAP_REGEO_URL)
|
||||
.queryParam("key", amapKey)
|
||||
.queryParam("location", location)
|
||||
.queryParam("extensions", "base")
|
||||
.queryParam("batch", "false")
|
||||
.queryParam("output", "JSON")
|
||||
.build(true)
|
||||
.toUriString();
|
||||
|
||||
String body = restTemplate.getForObject(url, String.class);
|
||||
if (!StringUtils.hasText(body)) {
|
||||
log.warn("高德逆地理编码响应为空, location={}", location);
|
||||
return vo;
|
||||
}
|
||||
JSONObject response = JSONObject.parseObject(body);
|
||||
if (response == null) {
|
||||
log.warn("高德逆地理编码 JSON 解析失败, location={}", location);
|
||||
return vo;
|
||||
}
|
||||
if (!"1".equals(response.getString("status"))) {
|
||||
String info = response.getString("info");
|
||||
log.warn("高德逆地理编码失败: status={}, info={}, infocode={}",
|
||||
response.getString("status"), info, response.getString("infocode"));
|
||||
return vo;
|
||||
}
|
||||
|
||||
JSONObject regeocode = response.getJSONObject("regeocode");
|
||||
if (regeocode == null) {
|
||||
return vo;
|
||||
}
|
||||
JSONObject addressComponent = regeocode.getJSONObject("addressComponent");
|
||||
if (addressComponent == null) {
|
||||
return vo;
|
||||
}
|
||||
|
||||
String province = nullToEmpty(addressComponent.getString("province"));
|
||||
String district = nullToEmpty(addressComponent.getString("district"));
|
||||
vo.setProvince(province);
|
||||
vo.setDistrict(district);
|
||||
|
||||
String cityName = resolveCityName(addressComponent, province);
|
||||
vo.setCityName(cityName);
|
||||
return vo;
|
||||
} catch (Exception e) {
|
||||
log.warn("高德逆地理编码异常, longitude={}, latitude={}, err={}",
|
||||
longitude, latitude, e.getMessage(), e);
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 城市字段:普通城市为字符串;部分省直辖为 [];直辖市可能用省名表示
|
||||
*/
|
||||
private static String resolveCityName(JSONObject addressComponent, String province) {
|
||||
Object cityRaw = addressComponent.get("city");
|
||||
if (cityRaw instanceof JSONArray) {
|
||||
JSONArray arr = (JSONArray) cityRaw;
|
||||
if (!arr.isEmpty()) {
|
||||
return nullToEmpty(arr.getString(0));
|
||||
}
|
||||
return province;
|
||||
}
|
||||
String city = addressComponent.getString("city");
|
||||
if (StringUtils.hasText(city) && !"[]".equals(city)) {
|
||||
return city.trim();
|
||||
}
|
||||
return province;
|
||||
}
|
||||
|
||||
private static String nullToEmpty(String s) {
|
||||
return s == null ? "" : s.trim();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.ruoyi.oa.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.ruoyi.common.core.domain.PageQuery;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.oa.domain.OaCity;
|
||||
import com.ruoyi.oa.domain.bo.OaCityBo;
|
||||
import com.ruoyi.oa.domain.vo.OaCityVo;
|
||||
import com.ruoyi.oa.mapper.OaCityMapper;
|
||||
import com.ruoyi.oa.service.IOaCityService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class OaCityServiceImpl implements IOaCityService {
|
||||
|
||||
private final OaCityMapper baseMapper;
|
||||
|
||||
@Override
|
||||
public OaCityVo queryById(Long cityId) {
|
||||
return baseMapper.selectVoById(cityId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableDataInfo<OaCityVo> queryPageList(OaCityBo bo, PageQuery pageQuery) {
|
||||
IPage<OaCityVo> page = baseMapper.selectVoPage(pageQuery.build(), buildQueryWrapper(bo));
|
||||
TableDataInfo<OaCityVo> tableDataInfo = new TableDataInfo<>(page.getRecords(), page.getTotal());
|
||||
return tableDataInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OaCityVo> queryList(OaCityBo bo) {
|
||||
return baseMapper.selectVoList(buildQueryWrapper(bo));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean insertByBo(OaCityBo bo) {
|
||||
OaCity add = BeanUtil.toBean(bo, OaCity.class);
|
||||
boolean flag = baseMapper.insert(add) > 0;
|
||||
if (flag) {
|
||||
bo.setCityId(add.getCityId());
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean updateByBo(OaCityBo bo) {
|
||||
OaCity update = BeanUtil.toBean(bo, OaCity.class);
|
||||
return baseMapper.updateById(update) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
||||
return baseMapper.deleteBatchIds(ids) > 0;
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<OaCity> buildQueryWrapper(OaCityBo bo) {
|
||||
LambdaQueryWrapper<OaCity> lqw = Wrappers.lambdaQuery();
|
||||
lqw.like(StringUtils.isNotBlank(bo.getCountryName()), OaCity::getCountryName, bo.getCountryName());
|
||||
lqw.like(StringUtils.isNotBlank(bo.getCityName()), OaCity::getCityName, bo.getCityName());
|
||||
lqw.eq(bo.getStatus() != null, OaCity::getStatus, bo.getStatus());
|
||||
lqw.orderByDesc(OaCity::getCreateTime);
|
||||
return lqw;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,35 @@
|
||||
package com.ruoyi.oa.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.common.core.domain.PageQuery;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.helper.LoginHelper;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.core.domain.PageQuery;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.ruoyi.hrm.domain.bo.HrmTravelReqBo;
|
||||
import com.ruoyi.hrm.domain.vo.HrmTravelReqVo;
|
||||
import com.ruoyi.hrm.service.IHrmTravelReqService;
|
||||
import com.ruoyi.oa.domain.OaProjectReport;
|
||||
import com.ruoyi.oa.domain.bo.OaProjectReportBo;
|
||||
import com.ruoyi.oa.domain.vo.OaProjectReportVo;
|
||||
import com.ruoyi.oa.domain.vo.OaProjectTravelCompareVo;
|
||||
import com.ruoyi.oa.domain.vo.ProjectReportCardVo;
|
||||
import com.ruoyi.oa.domain.vo.ProjectReportPieVo;
|
||||
import com.ruoyi.oa.domain.vo.ProjectReportTrendVo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.ruoyi.oa.domain.bo.OaProjectReportBo;
|
||||
import com.ruoyi.oa.domain.vo.OaProjectReportVo;
|
||||
import com.ruoyi.oa.domain.OaProjectReport;
|
||||
import com.ruoyi.oa.mapper.OaProjectReportMapper;
|
||||
import com.ruoyi.oa.service.IOaProjectReportService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 项目报工Service业务层处理
|
||||
@@ -37,6 +42,7 @@ import java.util.*;
|
||||
public class OaProjectReportServiceImpl implements IOaProjectReportService {
|
||||
|
||||
private final OaProjectReportMapper baseMapper;
|
||||
private final IHrmTravelReqService hrmTravelReqService;
|
||||
|
||||
/**
|
||||
* 查询项目报工
|
||||
@@ -64,6 +70,77 @@ public class OaProjectReportServiceImpl implements IOaProjectReportService {
|
||||
return baseMapper.selectAll(bo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableDataInfo<OaProjectTravelCompareVo> getTravelCompareList(LocalDate start, LocalDate end, String nickName, String workPlace, PageQuery pageQuery) {
|
||||
int pageNum = pageQuery.getPageNum() == null || pageQuery.getPageNum() <= 0 ? 1 : pageQuery.getPageNum();
|
||||
int pageSize = pageQuery.getPageSize() == null || pageQuery.getPageSize() <= 0 ? 50 : pageQuery.getPageSize();
|
||||
Page<OaProjectReportVo> page = new Page<>(pageNum, pageSize);
|
||||
Page<OaProjectReportVo> reportPage = baseMapper.selectTravelCompareReportPage(page, start, end, nickName, workPlace);
|
||||
List<OaProjectReportVo> reportList = reportPage.getRecords();
|
||||
List<OaProjectTravelCompareVo> rows = new ArrayList<>();
|
||||
List<HrmTravelReqVo> travelList = hrmTravelReqService.queryList(new HrmTravelReqBo());
|
||||
|
||||
for (OaProjectReportVo report : reportList) {
|
||||
OaProjectTravelCompareVo vo = new OaProjectTravelCompareVo();
|
||||
vo.setNickName(report.getNickName());
|
||||
vo.setCompareDate(toLocalDate(report.getCreateTime()));
|
||||
vo.setWorkPlace(safeString(report.getWorkPlace()));
|
||||
vo.setReportId(report.getReportId());
|
||||
vo.setIsTrip(report.getIsTrip());
|
||||
vo.setTrip(report.getTrip());
|
||||
|
||||
if (report.getIsTrip() == null || report.getIsTrip() != 1) {
|
||||
vo.setTravelPlace("无出差记录");
|
||||
vo.setPass(false);
|
||||
vo.setCompareResult("该员工未出差");
|
||||
rows.add(vo);
|
||||
continue;
|
||||
}
|
||||
|
||||
HrmTravelReqVo matchedTravel = null;
|
||||
LocalDate reportDate = vo.getCompareDate();
|
||||
if (reportDate != null) {
|
||||
for (HrmTravelReqVo travel : travelList) {
|
||||
if (travel.getEmpId() == null || report.getUserId() == null || !travel.getEmpId().equals(report.getUserId())) {
|
||||
continue;
|
||||
}
|
||||
if (travel.getStartTime() == null) {
|
||||
continue;
|
||||
}
|
||||
LocalDate travelStart = toLocalDate(travel.getStartTime());
|
||||
LocalDate travelEnd = toLocalDate(travel.getActualEndTime() != null ? travel.getActualEndTime() : travel.getEndTime());
|
||||
if (travelStart == null || travelEnd == null) {
|
||||
continue;
|
||||
}
|
||||
if (!reportDate.isBefore(travelStart) && !reportDate.isAfter(travelEnd)) {
|
||||
matchedTravel = travel;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (matchedTravel == null) {
|
||||
vo.setTravelPlace("无出差记录");
|
||||
vo.setPass(false);
|
||||
vo.setCompareResult("该员工未出差");
|
||||
} else {
|
||||
String travelPlace = safeString(matchedTravel.getDestination());
|
||||
boolean pass = isLocationMatched(vo.getWorkPlace(), travelPlace);
|
||||
vo.setTravelPlace(travelPlace);
|
||||
vo.setPass(pass);
|
||||
vo.setCompareResult(pass ? "通过" : "异常");
|
||||
vo.setTravelBizId(matchedTravel.getBizId());
|
||||
}
|
||||
|
||||
rows.add(vo);
|
||||
}
|
||||
|
||||
TableDataInfo<OaProjectTravelCompareVo> table = TableDataInfo.build();
|
||||
table.setRows(rows);
|
||||
table.setTotal(reportPage.getTotal());
|
||||
return table;
|
||||
}
|
||||
|
||||
|
||||
private QueryWrapper<OaProjectReport> buildQueryWrapper(OaProjectReportBo bo) {
|
||||
Map<String, Object> params = bo.getParams();
|
||||
@@ -110,6 +187,45 @@ public class OaProjectReportServiceImpl implements IOaProjectReportService {
|
||||
return lqw;
|
||||
}
|
||||
|
||||
private LocalDate toLocalDate(Date date) {
|
||||
if (date == null) return null;
|
||||
return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
|
||||
}
|
||||
|
||||
private String safeString(String value) {
|
||||
return value == null ? "" : value.trim();
|
||||
}
|
||||
|
||||
private boolean isLocationMatched(String a, String b) {
|
||||
if (StringUtils.isBlank(a) || StringUtils.isBlank(b)) {
|
||||
return false;
|
||||
}
|
||||
String left = normalizeLocation(a);
|
||||
String right = normalizeLocation(b);
|
||||
return !left.isEmpty() && left.equals(right);
|
||||
}
|
||||
|
||||
private String normalizeLocation(String value) {
|
||||
String normalized = value.trim();
|
||||
if (normalized.length() >= 2) {
|
||||
normalized = normalized.substring(0, 2);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private <T> TableDataInfo<T> toPage(List<T> list, PageQuery pageQuery) {
|
||||
int pageNum = pageQuery.getPageNum() == null || pageQuery.getPageNum() <= 0 ? 1 : pageQuery.getPageNum();
|
||||
int pageSize = pageQuery.getPageSize() == null || pageQuery.getPageSize() <= 0 ? 50 : pageQuery.getPageSize();
|
||||
long total = list.size();
|
||||
int fromIndex = Math.min((pageNum - 1) * pageSize, list.size());
|
||||
int toIndex = Math.min(fromIndex + pageSize, list.size());
|
||||
List<T> records = list.subList(fromIndex, toIndex);
|
||||
TableDataInfo<T> tableDataInfo = TableDataInfo.build();
|
||||
tableDataInfo.setRows(records);
|
||||
tableDataInfo.setTotal(total);
|
||||
return tableDataInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增项目报工
|
||||
*/
|
||||
@@ -168,7 +284,7 @@ public class OaProjectReportServiceImpl implements IOaProjectReportService {
|
||||
* 批量删除项目报工
|
||||
*/
|
||||
@Override
|
||||
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
||||
public Boolean deleteWithValidByIds(List<Long> ids, Boolean isValid) {
|
||||
if(isValid){
|
||||
//TODO 做一些业务上的校验,判断是否需要校验
|
||||
}
|
||||
|
||||
@@ -68,7 +68,8 @@ public class OaProjectScheduleServiceImpl implements IOaProjectScheduleService {
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<OaProjectScheduleVo> queryPageList(OaProjectScheduleBo bo, PageQuery pageQuery) {
|
||||
QueryWrapper<OaProjectSchedule> lqw = buildQueryWrapper(bo);
|
||||
// 分页列表走自定义 Join SQL(XML 中存在 ops/op 别名)
|
||||
QueryWrapper<OaProjectSchedule> lqw = buildQueryWrapperJoin(bo);
|
||||
Page<OaProjectScheduleVo> result = baseMapper.selectVoPagePlus(pageQuery.build(), lqw);
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
@@ -78,11 +79,17 @@ public class OaProjectScheduleServiceImpl implements IOaProjectScheduleService {
|
||||
*/
|
||||
@Override
|
||||
public List<OaProjectScheduleVo> queryList(OaProjectScheduleBo bo) {
|
||||
QueryWrapper<OaProjectSchedule> lqw = buildQueryWrapper(bo);
|
||||
// 非分页列表走 BaseMapper 默认 SQL(无 ops/op 别名),避免出现 ops.del_flag 这类不存在的列引用
|
||||
QueryWrapper<OaProjectSchedule> lqw = buildQueryWrapperBase(bo);
|
||||
return baseMapper.selectVoList(lqw);
|
||||
}
|
||||
|
||||
private QueryWrapper<OaProjectSchedule> buildQueryWrapper(OaProjectScheduleBo bo) {
|
||||
/**
|
||||
* Join 场景查询条件(对应 XML: OaProjectScheduleMapper.selectVoPagePlus)
|
||||
* - 主表别名:ops (oa_project_schedule)
|
||||
* - 项目表别名:op (sys_oa_project)
|
||||
*/
|
||||
private QueryWrapper<OaProjectSchedule> buildQueryWrapperJoin(OaProjectScheduleBo bo) {
|
||||
Map<String, Object> params = bo.getParams();
|
||||
QueryWrapper<OaProjectSchedule> lqw = Wrappers.query();
|
||||
lqw.eq("ops.del_flag", 0);
|
||||
@@ -112,6 +119,27 @@ public class OaProjectScheduleServiceImpl implements IOaProjectScheduleService {
|
||||
return lqw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base 表场景查询条件(对应 BaseMapper 默认 SQL:FROM oa_project_schedule)
|
||||
* 仅使用 oa_project_schedule 表字段,严格对齐表结构:
|
||||
* - oa_project_schedule(project_id、schedule_id、...、del_flag)
|
||||
*/
|
||||
private QueryWrapper<OaProjectSchedule> buildQueryWrapperBase(OaProjectScheduleBo bo) {
|
||||
QueryWrapper<OaProjectSchedule> lqw = Wrappers.query();
|
||||
// TableLogic 字段,默认查询只取未删除
|
||||
lqw.eq("del_flag", 0);
|
||||
lqw.eq(bo.getProjectId() != null, "project_id", bo.getProjectId());
|
||||
lqw.eq(bo.getTemplateId() != null, "template_id", bo.getTemplateId());
|
||||
lqw.eq(bo.getCurrentStep() != null, "current_step", bo.getCurrentStep());
|
||||
lqw.eq(bo.getStatus() != null, "status", bo.getStatus());
|
||||
lqw.eq(bo.getSteward() != null, "steward", bo.getSteward());
|
||||
// 使用 startTime / endTime 进行范围筛选
|
||||
lqw.between(bo.getStartTime() != null && bo.getEndTime() != null, "start_time", bo.getStartTime(), bo.getEndTime());
|
||||
lqw.ge(bo.getStartTime() != null && bo.getEndTime() == null, "start_time", bo.getStartTime());
|
||||
lqw.le(bo.getStartTime() == null && bo.getEndTime() != null, "end_time", bo.getEndTime());
|
||||
return lqw;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增项目进度
|
||||
*/
|
||||
|
||||
@@ -95,13 +95,13 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<OaProjectScheduleStepVo> queryPageList(OaProjectScheduleStepBo bo, PageQuery pageQuery) {
|
||||
QueryWrapper<OaProjectScheduleStep> lqw = buildQueryWrapper(bo);
|
||||
QueryWrapper<OaProjectScheduleStep> lqw = buildQueryWrapper(bo, true);
|
||||
Page<OaProjectScheduleStepVo> result = baseMapper.selectVoPagePlus(pageQuery.build(), lqw,LoginHelper.getUserId());
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
@Override
|
||||
public TableDataInfo<OaProjectScheduleStepVo> queryPageListPage(OaProjectScheduleStepBo bo, PageQuery pageQuery) {
|
||||
QueryWrapper<OaProjectScheduleStep> lqw = buildQueryWrapper(bo);
|
||||
QueryWrapper<OaProjectScheduleStep> lqw = buildQueryWrapper(bo, true);
|
||||
Page<OaProjectScheduleStepVo> result = baseMapper.selectVoPageNew(pageQuery.build(), lqw);
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
@@ -113,22 +113,13 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
private QueryWrapper<OaProjectScheduleStep> buildQueryMyWrapper(OaProjectScheduleStepBo bo) {
|
||||
Map<String, Object> params = bo.getParams();
|
||||
QueryWrapper<OaProjectScheduleStep> lqw = Wrappers.query();
|
||||
lqw.eq(bo.getScheduleId() != null, "opss.schedule_id", bo.getScheduleId());
|
||||
lqw.eq("opss.del_flag", 0);
|
||||
lqw.eq(bo.getStepOrder() != null, "opss.step_order", bo.getStepOrder());
|
||||
lqw.like(StringUtils.isNotBlank(bo.getStepName()), "opss.step_name", bo.getStepName());
|
||||
lqw.eq(bo.getStatus() != null, "opss.status", bo.getStatus());
|
||||
appendCommonStepFilters(lqw, bo, "opss");
|
||||
// 默认将负责人设置为当前用户
|
||||
String currentUser = LoginHelper.getNickName();
|
||||
lqw.eq(StringUtils.isNotBlank(currentUser), "opss.node_header", currentUser);
|
||||
lqw.eq(bo.getSupplierId() != null, "opss.supplier_id", bo.getSupplierId());
|
||||
//根据开始时间和结束时间作为范围判断planEnd
|
||||
lqw.ge(bo.getStartTime() != null, "opss.plan_end", bo.getStartTime());
|
||||
lqw.le(bo.getEndTime() != null, "opss.plan_end", bo.getEndTime());
|
||||
lqw.eq(StringUtils.isNotBlank(currentUser), stepCol("opss", "node_header"), currentUser);
|
||||
// 按时间倒序排列,已完成的排在最后
|
||||
lqw.orderByDesc("opss.plan_end");
|
||||
lqw.orderByDesc(stepCol("opss", "plan_end"));
|
||||
lqw.orderByAsc("opss.status = 2"); // 状态为2表示已完成,将其排在最后
|
||||
return lqw;
|
||||
}
|
||||
@@ -137,26 +128,42 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS
|
||||
*/
|
||||
@Override
|
||||
public List<OaProjectScheduleStepVo> queryList(OaProjectScheduleStepBo bo) {
|
||||
QueryWrapper<OaProjectScheduleStep> lqw = buildQueryWrapper(bo);
|
||||
QueryWrapper<OaProjectScheduleStep> lqw = buildQueryWrapper(bo, false);
|
||||
return baseMapper.selectVoList(lqw);
|
||||
}
|
||||
|
||||
private QueryWrapper<OaProjectScheduleStep> buildQueryWrapper(OaProjectScheduleStepBo bo) {
|
||||
Map<String, Object> params = bo.getParams();
|
||||
@Override
|
||||
public List<OaProjectScheduleStepVo> selectProjectScheduleStepList(Long scheduleId) {
|
||||
return baseMapper.selectProjectScheduleStepList(scheduleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param forJoinSql true:XML 中带 opss/schedule 等多表别名,条件须加 opss. 前缀,避免 schedule_id 等列歧义
|
||||
*/
|
||||
private QueryWrapper<OaProjectScheduleStep> buildQueryWrapper(OaProjectScheduleStepBo bo, boolean forJoinSql) {
|
||||
QueryWrapper<OaProjectScheduleStep> lqw = Wrappers.query();
|
||||
lqw.eq(bo.getScheduleId() != null, "opss.schedule_id", bo.getScheduleId());
|
||||
lqw.eq("opss.del_flag", 0);
|
||||
lqw.eq(bo.getStepOrder() != null, "opss.step_order", bo.getStepOrder());
|
||||
lqw.like(StringUtils.isNotBlank(bo.getStepName()), "opss.step_name", bo.getStepName());
|
||||
lqw.eq(bo.getStatus() != null, "opss.status", bo.getStatus());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getNodeHeader()), "opss.node_header", bo.getNodeHeader());
|
||||
lqw.eq(bo.getSupplierId() != null, "opss.supplier_id", bo.getSupplierId());
|
||||
//根据开始时间和结束时间作为范围判断planEnd
|
||||
lqw.ge(bo.getStartTime() != null, "opss.plan_end", bo.getStartTime());
|
||||
lqw.le(bo.getEndTime() != null, "opss.plan_end", bo.getEndTime());
|
||||
String alias = forJoinSql ? "opss" : null;
|
||||
appendCommonStepFilters(lqw, bo, alias);
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getNodeHeader()), stepCol(alias, "node_header"), bo.getNodeHeader());
|
||||
return lqw;
|
||||
}
|
||||
|
||||
private static String stepCol (String tableAlias, String column) {
|
||||
return StringUtils.isBlank(tableAlias) ? column : tableAlias + "." + column;
|
||||
}
|
||||
|
||||
/** 步骤表公共筛选(与 oa_project_schedule 联表时须使用别名 opss) */
|
||||
private void appendCommonStepFilters (QueryWrapper<OaProjectScheduleStep> lqw, OaProjectScheduleStepBo bo, String alias) {
|
||||
lqw.eq(stepCol(alias, "del_flag"), 0);
|
||||
lqw.eq(bo.getScheduleId() != null, stepCol(alias, "schedule_id"), bo.getScheduleId());
|
||||
lqw.eq(bo.getStepOrder() != null, stepCol(alias, "step_order"), bo.getStepOrder());
|
||||
lqw.like(StringUtils.isNotBlank(bo.getStepName()), stepCol(alias, "step_name"), bo.getStepName());
|
||||
lqw.eq(bo.getStatus() != null, stepCol(alias, "status"), bo.getStatus());
|
||||
lqw.eq(bo.getSupplierId() != null, stepCol(alias, "supplier_id"), bo.getSupplierId());
|
||||
lqw.ge(bo.getStartTime() != null, stepCol(alias, "plan_end"), bo.getStartTime());
|
||||
lqw.le(bo.getEndTime() != null, stepCol(alias, "plan_end"), bo.getEndTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增项目进度步骤跟踪
|
||||
*/
|
||||
|
||||
@@ -12,23 +12,30 @@ import com.ruoyi.common.core.domain.PageQuery;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.ruoyi.oa.domain.bo.OaProjectScheduleBo;
|
||||
import com.ruoyi.oa.domain.bo.SysOaWarehouseDetailBo;
|
||||
import com.ruoyi.oa.domain.dto.ProjectActivityDTO;
|
||||
import com.ruoyi.oa.domain.dto.ProjectDataDTO;
|
||||
import com.ruoyi.oa.domain.dto.ProjectScheduleStepStatsDto;
|
||||
import com.ruoyi.oa.domain.vo.*;
|
||||
import com.ruoyi.oa.service.CodeGeneratorService;
|
||||
import com.ruoyi.oa.service.IExchangeRateService;
|
||||
import com.ruoyi.oa.service.IOaProjectScheduleService;
|
||||
import com.ruoyi.oa.service.IOaProjectScheduleStepService;
|
||||
import com.ruoyi.oa.service.ISysOaTaskService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.ruoyi.oa.domain.bo.SysOaProjectBo;
|
||||
import com.ruoyi.oa.domain.SysOaProject;
|
||||
import com.ruoyi.oa.mapper.OaProjectScheduleStepMapper;
|
||||
import com.ruoyi.oa.mapper.SysOaProjectMapper;
|
||||
import com.ruoyi.oa.service.ISysOaProjectService;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -43,6 +50,14 @@ public class SysOaProjectServiceImpl implements ISysOaProjectService {
|
||||
|
||||
private final SysOaProjectMapper baseMapper;
|
||||
|
||||
private final ISysOaTaskService sysOaTaskService;
|
||||
|
||||
private final IOaProjectScheduleService oaProjectScheduleService;
|
||||
|
||||
private final IOaProjectScheduleStepService oaProjectScheduleStepService;
|
||||
|
||||
private final OaProjectScheduleStepMapper oaProjectScheduleStepMapper;
|
||||
|
||||
@Autowired
|
||||
private CodeGeneratorService codeGeneratorService;
|
||||
@Autowired
|
||||
@@ -182,14 +197,46 @@ public class SysOaProjectServiceImpl implements ISysOaProjectService {
|
||||
}
|
||||
}
|
||||
|
||||
if (Boolean.TRUE.equals(bo.getScheduleStats()) && result.getRecords() != null && !result.getRecords().isEmpty()) {
|
||||
List<Long> projectIds = result.getRecords().stream()
|
||||
.map(SysOaProjectVo::getProjectId)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
if (!projectIds.isEmpty()) {
|
||||
List<ProjectScheduleStepStatsDto> statRows = oaProjectScheduleStepMapper.selectStepStatsGroupByProjectId(projectIds);
|
||||
Map<Long, ProjectScheduleStepStatsDto> statMap = statRows.stream()
|
||||
.collect(Collectors.toMap(ProjectScheduleStepStatsDto::getProjectId, Function.identity(), (a, b) -> a));
|
||||
for (SysOaProjectVo vo : result.getRecords()) {
|
||||
ProjectScheduleStepStatsDto s = statMap.get(vo.getProjectId());
|
||||
if (s != null) {
|
||||
vo.setScheduleStepTotal(s.getTotalNodes());
|
||||
vo.setScheduleStepCompleted(s.getCompletedNodes());
|
||||
vo.setScheduleStepPendingAccept(s.getPendingAcceptNodes());
|
||||
} else {
|
||||
vo.setScheduleStepTotal(0L);
|
||||
vo.setScheduleStepCompleted(0L);
|
||||
vo.setScheduleStepPendingAccept(0L);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
private QueryWrapper<SysOaProject> buildAliasPQueryWrapper(SysOaProjectBo bo) {
|
||||
Map<String, Object> params = bo.getParams();
|
||||
QueryWrapper<SysOaProject> qw = Wrappers.query();
|
||||
if (StringUtils.isNotBlank(bo.getKeyword())) {
|
||||
String kw = bo.getKeyword().trim();
|
||||
qw.and(w -> w.like("p.project_name", kw)
|
||||
.or().like("p.project_num", kw)
|
||||
.or().like("p.project_code", kw));
|
||||
} else {
|
||||
qw.like(StringUtils.isNotBlank(bo.getProjectName()), "p.project_name", bo.getProjectName());
|
||||
qw.like(StringUtils.isNotBlank(bo.getProjectNum()), "p.project_num", bo.getProjectNum());
|
||||
qw.like(StringUtils.isNotBlank(bo.getProjectCode()), "p.project_code", bo.getProjectCode());
|
||||
}
|
||||
qw.eq(bo.getProductStatus() != null, "p.product_status", bo.getProductStatus());
|
||||
qw.eq(bo.getTradeType() != null, "p.trade_type", bo.getTradeType());
|
||||
if (bo.getPrePay() != null && bo.getPrePay() > 0) {
|
||||
@@ -204,6 +251,7 @@ public class SysOaProjectServiceImpl implements ISysOaProjectService {
|
||||
}
|
||||
qw.eq(bo.getCustomerId() != null, "p.customer_id", bo.getCustomerId());
|
||||
qw.orderByDesc("p.is_top").orderByDesc("p.create_time");
|
||||
qw.groupBy("p.project_id");
|
||||
return qw;
|
||||
}
|
||||
|
||||
@@ -219,9 +267,16 @@ public class SysOaProjectServiceImpl implements ISysOaProjectService {
|
||||
private LambdaQueryWrapper<SysOaProject> buildQueryWrapper(SysOaProjectBo bo) {
|
||||
Map<String, Object> params = bo.getParams();
|
||||
LambdaQueryWrapper<SysOaProject> lqw = Wrappers.lambdaQuery();
|
||||
if (StringUtils.isNotBlank(bo.getKeyword())) {
|
||||
String kw = bo.getKeyword().trim();
|
||||
lqw.and(w -> w.like(SysOaProject::getProjectName, kw)
|
||||
.or().like(SysOaProject::getProjectNum, kw)
|
||||
.or().like(SysOaProject::getProjectCode, kw));
|
||||
} else {
|
||||
lqw.like(StringUtils.isNotBlank(bo.getProjectName()), SysOaProject::getProjectName, bo.getProjectName());
|
||||
lqw.like(StringUtils.isNotBlank(bo.getProjectNum()), SysOaProject::getProjectNum, bo.getProjectNum());
|
||||
lqw.like(StringUtils.isNotBlank(bo.getProjectCode()), SysOaProject::getProjectCode, bo.getProjectCode());
|
||||
}
|
||||
//新增生产结项状态筛选
|
||||
lqw.eq(bo.getProductStatus() != null, SysOaProject::getProductStatus, bo.getProductStatus());
|
||||
lqw.eq(bo.getTradeType() != null, SysOaProject::getTradeType, bo.getTradeType());
|
||||
@@ -587,4 +642,37 @@ public class SysOaProjectServiceImpl implements ISysOaProjectService {
|
||||
return baseMapper.getMaxCodeProject(prefix);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OaProjectDashboardVo getProjectDashboard(Long projectId) {
|
||||
SysOaProjectVo project = queryById(projectId);
|
||||
if (project == null) {
|
||||
return null;
|
||||
}
|
||||
List<SysOaTaskVo> tasks = sysOaTaskService.listDashboardTasks(projectId);
|
||||
|
||||
OaProjectScheduleBo scheduleBo = new OaProjectScheduleBo();
|
||||
scheduleBo.setProjectId(projectId);
|
||||
List<OaProjectScheduleVo> schedules = oaProjectScheduleService.queryList(scheduleBo);
|
||||
|
||||
List<OaProjectScheduleStepVo> steps = new ArrayList<>();
|
||||
for (OaProjectScheduleVo sch : schedules) {
|
||||
List<OaProjectScheduleStepVo> part = oaProjectScheduleStepService.selectProjectScheduleStepList(sch.getScheduleId());
|
||||
if (part == null) {
|
||||
continue;
|
||||
}
|
||||
for (OaProjectScheduleStepVo st : part) {
|
||||
st.setProjectId(projectId);
|
||||
st.setProjectName(project.getProjectName());
|
||||
}
|
||||
steps.addAll(part);
|
||||
}
|
||||
|
||||
OaProjectDashboardVo vo = new OaProjectDashboardVo();
|
||||
vo.setProject(project);
|
||||
vo.setTasks(tasks);
|
||||
vo.setSchedules(schedules);
|
||||
vo.setSteps(steps);
|
||||
return vo;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -135,6 +135,11 @@ public class SysOaTaskServiceImpl implements ISysOaTaskService {
|
||||
return baseMapper.listDocumentProject(projectId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SysOaTaskVo> listDashboardTasks(Long projectId) {
|
||||
return baseMapper.selectDashboardTasksByProjectId(projectId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增:自定义查询任务列表(Plus版)
|
||||
*/
|
||||
@@ -149,6 +154,7 @@ public class SysOaTaskServiceImpl implements ISysOaTaskService {
|
||||
QueryWrapper<SysOaTask> lqw = Wrappers.query();
|
||||
lqw.eq("sot.del_flag", 0);
|
||||
lqw.eq(bo.getProjectId()!=null, "sot.project_id", bo.getProjectId());
|
||||
lqw.eq(bo.getTaskId()!=null, "sot.task_id", bo.getTaskId());
|
||||
lqw.eq(bo.getCreateUserId()!=null, "sot.create_user_id", bo.getCreateUserId());
|
||||
lqw.eq(bo.getWorkerId()!=null, "sot.worker_id", bo.getWorkerId());
|
||||
lqw.eq(bo.getProjectId()!=null, "sot.project_id", bo.getProjectId());
|
||||
|
||||
@@ -195,11 +195,54 @@
|
||||
left join sys_user su on su.user_id = opr.user_id
|
||||
left join sys_dept sd on su.dept_id = sd.dept_id
|
||||
WHERE opr.del_flag = 0
|
||||
AND DATE(opr.create_time)
|
||||
BETWEEN DATE_FORMAT(#{start}, '%Y-%m-%d')
|
||||
AND DATE_FORMAT(#{end}, '%Y-%m-%d')
|
||||
<if test="start != null">
|
||||
AND DATE(opr.create_time) >= DATE_FORMAT(#{start}, '%Y-%m-%d')
|
||||
</if>
|
||||
<if test="end != null">
|
||||
AND DATE(opr.create_time) <= DATE_FORMAT(#{end}, '%Y-%m-%d')
|
||||
</if>
|
||||
ORDER BY opr.create_time DESC
|
||||
</select>
|
||||
|
||||
<select id="selectTravelCompareReportPage" resultType="com.ruoyi.oa.domain.vo.OaProjectReportVo">
|
||||
select opr.report_id,
|
||||
opr.user_id,
|
||||
opr.is_trip,
|
||||
opr.work_place,
|
||||
opr.project_id,
|
||||
opr.content,
|
||||
opr.create_time,
|
||||
opr.create_by,
|
||||
opr.update_time,
|
||||
opr.update_by,
|
||||
opr.del_flag,
|
||||
opr.remark,
|
||||
op.project_name,
|
||||
op.project_num,
|
||||
op.project_code,
|
||||
su.nick_name,
|
||||
opr.work_type,
|
||||
sd.dept_name
|
||||
from oa_project_report opr
|
||||
left join sys_oa_project op on opr.project_id = op.project_id
|
||||
left join sys_user su on su.user_id = opr.user_id
|
||||
left join sys_dept sd on su.dept_id = sd.dept_id
|
||||
<where>
|
||||
opr.del_flag = 0
|
||||
<if test="start != null">
|
||||
AND DATE(opr.create_time) >= DATE_FORMAT(#{start}, '%Y-%m-%d')
|
||||
</if>
|
||||
<if test="end != null">
|
||||
AND DATE(opr.create_time) <= DATE_FORMAT(#{end}, '%Y-%m-%d')
|
||||
</if>
|
||||
<if test="nickName != null and nickName != ''">
|
||||
AND su.nick_name LIKE CONCAT('%', #{nickName}, '%')
|
||||
</if>
|
||||
<if test="workPlace != null and workPlace != ''">
|
||||
AND opr.work_place LIKE CONCAT('%', #{workPlace}, '%')
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY opr.create_time DESC
|
||||
LIMIT 20
|
||||
</select>
|
||||
|
||||
<select id="getSummaryData" resultType="com.ruoyi.oa.domain.vo.OaProjectReportVo">
|
||||
|
||||
@@ -204,8 +204,8 @@
|
||||
status,
|
||||
header
|
||||
from oa_project_schedule_step opss
|
||||
WHERE schedule_id = #{scheduleId}
|
||||
AND step_order = #{currentStep}
|
||||
WHERE opss.schedule_id = #{scheduleId}
|
||||
AND opss.step_order = #{currentStep}
|
||||
and use_flag = '1'
|
||||
AND del_flag = '0'
|
||||
</select>
|
||||
@@ -258,5 +258,46 @@
|
||||
${ew.customSqlSegment}
|
||||
</select>
|
||||
|
||||
<!--
|
||||
综合看板:按 schedule_id 查询步骤列表
|
||||
要求:
|
||||
- 删除 del_flag(表无该字段)
|
||||
- 不使用 opss.* 前缀(不定义别名)
|
||||
- 仅保留 WHERE schedule_id = #{scheduleId}
|
||||
-->
|
||||
<select id="selectProjectScheduleStepList"
|
||||
parameterType="com.ruoyi.oa.domain.OaProjectScheduleStep"
|
||||
resultMap="OaProjectScheduleStepResult">
|
||||
SELECT
|
||||
track_id, accessory, schedule_id, step_order, step_name,
|
||||
plan_start, plan_end, actual_start, actual_end, status,
|
||||
header, use_flag, batch_id, tab_node,
|
||||
first_level_node, second_level_node,
|
||||
start_time, original_end_time, end_time,
|
||||
node_header, related_docs, related_images,
|
||||
specification, sort_num, supplier_id,
|
||||
requirement_file, other,
|
||||
create_by, create_time, update_by, update_time
|
||||
FROM oa_project_schedule_step
|
||||
WHERE schedule_id = #{scheduleId}
|
||||
</select>
|
||||
|
||||
<select id="selectStepStatsGroupByProjectId"
|
||||
resultType="com.ruoyi.oa.domain.dto.ProjectScheduleStepStatsDto">
|
||||
SELECT
|
||||
sch.project_id AS projectId,
|
||||
COUNT(step.track_id) AS totalNodes,
|
||||
IFNULL(SUM(CASE WHEN step.status = 2 THEN 1 ELSE 0 END), 0) AS completedNodes,
|
||||
IFNULL(SUM(CASE WHEN step.status = 1 THEN 1 ELSE 0 END), 0) AS pendingAcceptNodes
|
||||
FROM oa_project_schedule sch
|
||||
INNER JOIN oa_project_schedule_step step ON step.schedule_id = sch.schedule_id
|
||||
WHERE sch.del_flag = '0'
|
||||
AND step.del_flag = '0'
|
||||
AND sch.project_id IN
|
||||
<foreach collection="projectIds" item="pid" open="(" separator="," close=")">
|
||||
#{pid}
|
||||
</foreach>
|
||||
GROUP BY sch.project_id
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -310,9 +310,15 @@
|
||||
TIMESTAMPDIFF(DAY, NOW(), p.postpone_time) AS remainTime,
|
||||
p.customer_id AS customerId,
|
||||
c.name AS customerName,
|
||||
p.is_top AS isTop
|
||||
p.is_top AS isTop,
|
||||
COUNT(sot.task_id) AS taskTotalCount ,
|
||||
SUM(CASE WHEN sot.status = 1 THEN 1 ELSE 0 END) AS taskFinishCount,
|
||||
COUNT(ops.schedule_id) AS scheduleTotalCount,
|
||||
SUM(CASE WHEN ops.status = 2 THEN 1 ELSE 0 END) AS scheduleFinishCount
|
||||
FROM sys_oa_project p
|
||||
LEFT JOIN oa_customer c ON p.customer_id = c.customer_id
|
||||
left join sys_oa_task sot on sot.project_id = p.project_id and sot.del_flag = 0
|
||||
left join oa_project_schedule ops on ops.project_id = p.project_id and ops.del_flag = 0
|
||||
${ew.getCustomSqlSegment}
|
||||
</select>
|
||||
|
||||
|
||||
@@ -474,6 +474,41 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
|
||||
</select>
|
||||
|
||||
<!-- 综合看板:LEFT JOIN 进度步骤 del_flag=0、use_flag=1(与步骤表一致即可展示,不再依赖进度主表 project 联表) -->
|
||||
<select id="selectDashboardTasksByProjectId" resultType="com.ruoyi.oa.domain.vo.SysOaTaskVo">
|
||||
SELECT
|
||||
sot.task_id AS taskId,
|
||||
sot.project_id AS projectId,
|
||||
sop.project_code AS projectCode,
|
||||
sot.task_title AS taskTitle,
|
||||
sot.task_type AS taskType,
|
||||
sot.task_grade AS taskGrade,
|
||||
su1.nick_name AS createUserNickName,
|
||||
su2.nick_name AS workerNickName,
|
||||
sot.state AS state,
|
||||
sot.finish_time AS finishTime,
|
||||
sot.track_id AS trackId,
|
||||
opss.tab_node AS tabNode,
|
||||
opss.first_level_node AS firstLevelNode,
|
||||
opss.second_level_node AS secondLevelNode,
|
||||
opss.step_name AS scheduleStepName,
|
||||
opss.status AS scheduleStatus,
|
||||
opss.plan_start AS schedulePlanStart,
|
||||
opss.plan_end AS schedulePlanEnd,
|
||||
opss.header AS scheduleHeader
|
||||
FROM sys_oa_task sot
|
||||
LEFT JOIN sys_user su1 ON su1.user_id = sot.create_user_id
|
||||
LEFT JOIN sys_user su2 ON su2.user_id = sot.worker_id
|
||||
LEFT JOIN sys_oa_project sop ON sop.project_id = sot.project_id
|
||||
LEFT JOIN oa_project_schedule_step opss
|
||||
ON opss.track_id = sot.track_id
|
||||
AND opss.del_flag = '0'
|
||||
AND opss.use_flag = 1
|
||||
WHERE sot.project_id = #{projectId}
|
||||
AND sot.del_flag = '0'
|
||||
ORDER BY sot.create_time DESC
|
||||
</select>
|
||||
|
||||
<select id="queryListPlus" parameterType="com.ruoyi.oa.domain.bo.SysOaTaskBo" resultType="com.ruoyi.oa.domain.vo.SysOaTaskVo">
|
||||
SELECT
|
||||
task_id AS taskId,
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"url": "https://gitee.com/KonBAI-Q/ruoyi-flowable-plus.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@amap/amap-jsapi-loader": "^1.0.1",
|
||||
"@babel/parser": "7.7.4",
|
||||
"@handsontable/vue": "^15.3.0",
|
||||
"@jiaminghi/data-view": "^2.10.0",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<title>
|
||||
<%= webpackConfig.name %>
|
||||
</title>
|
||||
|
||||
<!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
|
||||
<style>
|
||||
html,
|
||||
@@ -18,7 +19,7 @@
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
|
||||
.chromeframe {
|
||||
margin: 0.2em 0;
|
||||
|
||||
@@ -57,3 +57,18 @@ export function getAppropriationStats (query) {
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function checkAppropriationOcrHealth () {
|
||||
return request({ url: '/hrm/appropriation/ocr-health', method: 'get' })
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过ossId触发发票OCR识别(返回识别条目,不保存)
|
||||
*/
|
||||
export function ocrAppropriationInvoice (ossId) {
|
||||
return request({
|
||||
url: '/hrm/appropriation/ocr-by-oss',
|
||||
method: 'post',
|
||||
params: { ossId }
|
||||
})
|
||||
}
|
||||
|
||||
@@ -16,7 +16,13 @@ export function readCc(ccId) {
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
// 标记抄送为未读
|
||||
export function unreadCc(ccId) {
|
||||
return request({
|
||||
url: `/hrm/flow/cc/${ccId}/unread`,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
// 手动抄送
|
||||
export function addCc(data) {
|
||||
return request({
|
||||
|
||||
@@ -254,9 +254,4 @@ export function listAssignTask (instId) {
|
||||
url: `/hrm/flow/instance/tasks/${instId}`,
|
||||
method: 'get'
|
||||
})
|
||||
|
||||
/**
|
||||
* 查询当前用户的审批历史
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
9
ruoyi-ui/src/api/hrm/myApply.js
Normal file
9
ruoyi-ui/src/api/hrm/myApply.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function listMyApply(query) {
|
||||
return request({
|
||||
url: '/hrm/my-apply/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
@@ -47,3 +47,18 @@ export function allReimburseReq(query) {
|
||||
})
|
||||
}
|
||||
|
||||
export function checkReimburseOcrHealth() {
|
||||
return request({ url: '/hrm/reimburse/ocr-health', method: 'get' })
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过ossId触发发票OCR识别(返回识别条目,不保存)
|
||||
*/
|
||||
export function ocrReimburseInvoice(ossId) {
|
||||
return request({
|
||||
url: '/hrm/reimburse/ocr-by-oss',
|
||||
method: 'post',
|
||||
params: { ossId }
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -46,3 +46,22 @@ export function allTravelReq(query) {
|
||||
params: query
|
||||
})
|
||||
}
|
||||
export function earlyEndTravel(bizId) {
|
||||
return request({
|
||||
url: `/hrm/travel/earlyEnd/${bizId}`,
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
export function getAmapKey() {
|
||||
return request({
|
||||
url: '/hrm/travel/amapKey',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
export function getAmapSecurityKey() {
|
||||
return request({
|
||||
url: '/hrm/travel/amapSecurityKey',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user