fix(产品管理、项目管理、登录密码校验、工作报告): 修复用户们提出的一系列问题。
This commit is contained in:
@@ -23,6 +23,11 @@ public final class ProductObjectConstants {
|
||||
*/
|
||||
public static final String STATUS_PAUSED = "paused";
|
||||
|
||||
/**
|
||||
* 产品废弃状态。
|
||||
*/
|
||||
public static final String STATUS_ABANDONED = "abandoned";
|
||||
|
||||
/**
|
||||
* 产品自动编码前缀。
|
||||
*/
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.njcn.rdms.framework.common.util.object.BeanUtils;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductContextRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductCreateWithTeamReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductDeleteReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductOptionRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductOverviewSummaryRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductPageReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductRespVO;
|
||||
@@ -22,6 +23,8 @@ import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - 产品管理")
|
||||
@@ -82,6 +85,12 @@ public class ProductController {
|
||||
return success(productService.getProductOverviewSummary());
|
||||
}
|
||||
|
||||
@GetMapping("/options")
|
||||
@Operation(summary = "获取可绑定产品下拉选项")
|
||||
public CommonResult<List<ProductOptionRespVO>> getProductOptions() {
|
||||
return success(productService.getProductOptions());
|
||||
}
|
||||
|
||||
@PostMapping("/change-status")
|
||||
@Operation(summary = "变更产品状态")
|
||||
public CommonResult<Boolean> changeProductStatus(@Valid @RequestBody ProductStatusActionReqVO reqVO) {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.product.vo.product;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - 产品下拉选项 Response VO")
|
||||
@Data
|
||||
public class ProductOptionRespVO {
|
||||
|
||||
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "产品编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "CNPD2026001")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "灿能管理后台")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "产品方向字典值", example = "direction_value")
|
||||
private String directionCode;
|
||||
}
|
||||
@@ -48,5 +48,4 @@ public interface ProductMapper extends BaseMapperX<ProductDO> {
|
||||
.eq(ProductDO::getId, id)
|
||||
.eq(ProductDO::getStatusCode, statusCode));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,7 +38,9 @@ public final class AttachmentValidator {
|
||||
// 压缩
|
||||
"zip", "rar", "7z",
|
||||
// 媒体
|
||||
"mp4", "mp3"
|
||||
"mp4", "mp3",
|
||||
// 其他
|
||||
"sql", "xml", "json"
|
||||
);
|
||||
|
||||
/** 禁止的扩展名黑名单(即使在白名单也禁,兜底防可执行 / 脚本类)。 */
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.njcn.rdms.framework.common.pojo.PageResult;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductContextRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductCreateWithTeamReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductDeleteReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductOptionRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductOverviewSummaryRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductPageReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductRespVO;
|
||||
@@ -12,6 +13,8 @@ import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductS
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.setting.ProductSettingBaseInfoUpdateReqVO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.product.ProductDO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 产品 Service 接口
|
||||
*/
|
||||
@@ -89,6 +92,13 @@ public interface ProductService {
|
||||
*/
|
||||
ProductOverviewSummaryRespVO getProductOverviewSummary();
|
||||
|
||||
/**
|
||||
* 获取可绑定产品下拉选项。
|
||||
*
|
||||
* @return 产品下拉选项
|
||||
*/
|
||||
List<ProductOptionRespVO> getProductOptions();
|
||||
|
||||
/**
|
||||
* 变更产品状态
|
||||
*
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductC
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductContextRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductCreateWithTeamReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductDeleteReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductOptionRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductOverviewSummaryRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductPageReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductSaveReqVO;
|
||||
@@ -68,6 +69,10 @@ import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil
|
||||
@Service
|
||||
public class ProductServiceImpl implements ProductService {
|
||||
|
||||
private static final List<String> PRODUCT_BIND_EXCLUDED_STATUS_CODES = List.of(
|
||||
ProductObjectConstants.STATUS_PAUSED,
|
||||
ProductObjectConstants.STATUS_ABANDONED);
|
||||
|
||||
@Resource
|
||||
private ProductMapper productMapper;
|
||||
@Resource
|
||||
@@ -455,6 +460,29 @@ public class ProductServiceImpl implements ProductService {
|
||||
return respVO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProductOptionRespVO> getProductOptions() {
|
||||
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
|
||||
ObjectDataScope scope = objectDataScopeService.compute(loginUserId, ProductObjectConstants.OBJECT_TYPE);
|
||||
if (scope.getState() == ObjectDataScope.State.EMPTY) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
LambdaQueryWrapperX<ProductDO> wrapper = new LambdaQueryWrapperX<>();
|
||||
wrapper.notIn(ProductDO::getStatusCode, PRODUCT_BIND_EXCLUDED_STATUS_CODES);
|
||||
wrapper.orderByDesc(ProductDO::getCreateTime);
|
||||
applyProductScopeFilter(wrapper, scope);
|
||||
return productMapper.selectList(wrapper).stream()
|
||||
.map(product -> {
|
||||
ProductOptionRespVO respVO = new ProductOptionRespVO();
|
||||
respVO.setId(product.getId());
|
||||
respVO.setCode(product.getCode());
|
||||
respVO.setName(product.getName());
|
||||
respVO.setDirectionCode(product.getDirectionCode());
|
||||
return respVO;
|
||||
})
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 scope 算出 (statusCode, countValue) 行集,喂给 {@link #buildProductStatusCounts}。
|
||||
* EMPTY 直接空集;ALL 走原全表 GROUP BY SQL;ID_LIST 用 wrapper 取 status_code,Java 端 group + count。
|
||||
@@ -469,6 +497,24 @@ public class ProductServiceImpl implements ProductService {
|
||||
// ID_LIST
|
||||
LambdaQueryWrapperX<ProductDO> wrapper = new LambdaQueryWrapperX<>();
|
||||
wrapper.select(ProductDO::getStatusCode);
|
||||
applyProductScopeFilter(wrapper, scope);
|
||||
return productMapper.selectList(wrapper).stream()
|
||||
.filter(p -> p.getStatusCode() != null)
|
||||
.collect(Collectors.groupingBy(ProductDO::getStatusCode, Collectors.counting()))
|
||||
.entrySet().stream()
|
||||
.map(e -> {
|
||||
Map<String, Object> row = new HashMap<>();
|
||||
row.put("statusCode", e.getKey());
|
||||
row.put("countValue", e.getValue());
|
||||
return row;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void applyProductScopeFilter(LambdaQueryWrapperX<ProductDO> wrapper, ObjectDataScope scope) {
|
||||
if (scope.getState() != ObjectDataScope.State.ID_LIST) {
|
||||
return;
|
||||
}
|
||||
Set<Long> ids = scope.getIds();
|
||||
Set<String> dcs = scope.getDirectionCodes();
|
||||
wrapper.and(w -> {
|
||||
@@ -484,17 +530,6 @@ public class ProductServiceImpl implements ProductService {
|
||||
w.in(ProductDO::getDirectionCode, dcs);
|
||||
}
|
||||
});
|
||||
return productMapper.selectList(wrapper).stream()
|
||||
.filter(p -> p.getStatusCode() != null)
|
||||
.collect(Collectors.groupingBy(ProductDO::getStatusCode, Collectors.counting()))
|
||||
.entrySet().stream()
|
||||
.map(e -> {
|
||||
Map<String, Object> row = new HashMap<>();
|
||||
row.put("statusCode", e.getKey());
|
||||
row.put("countValue", e.getValue());
|
||||
return row;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -185,7 +185,7 @@ public class WorkReportCommonService {
|
||||
WeeklyReportRespVO respVO = latestDraft;
|
||||
respVO.setIsBusinessTrip(Boolean.TRUE.equals(reqVO.getIsBusinessTrip()));
|
||||
respVO.setTravelSegments(BeanUtils.toBean(defaultList(reqVO.getTravelSegments()), WeeklyReportTravelSegmentRespVO.class));
|
||||
respVO.setReviewItems(mergeReviewItems(reqVO.getReviewItems(), latestDraft.getReviewItems()));
|
||||
respVO.setReviewItems(mergeWeeklyReviewItems(reqVO.getReviewItems(), latestDraft.getReviewItems()));
|
||||
respVO.setPlanItems(mergePlanItems(reqVO.getPlanItems(), latestDraft.getPlanItems()));
|
||||
respVO.setTotalTravelDays(Boolean.TRUE.equals(reqVO.getIsBusinessTrip())
|
||||
? defaultIfNull(sumTravelDays(reqVO.getTravelSegments()))
|
||||
@@ -1132,6 +1132,45 @@ public class WorkReportCommonService {
|
||||
return merged;
|
||||
}
|
||||
|
||||
private List<PersonalReportReviewItemRespVO> mergeWeeklyReviewItems(List<PersonalReportReviewItemReqVO> currentItems,
|
||||
List<PersonalReportReviewItemRespVO> latestItems) {
|
||||
List<PersonalReportReviewItemRespVO> merged = BeanUtils.toBean(defaultList(currentItems),
|
||||
PersonalReportReviewItemRespVO.class);
|
||||
Map<String, PersonalReportReviewItemRespVO> latestItemMap = defaultList(latestItems).stream()
|
||||
.filter(Objects::nonNull)
|
||||
.filter(item -> StringUtils.hasText(item.getItemTitle()))
|
||||
.collect(Collectors.toMap(item -> normalizeMergeText(item.getItemTitle()),
|
||||
item -> item, (left, right) -> right, LinkedHashMap::new));
|
||||
Set<String> existingKeys = new LinkedHashSet<>();
|
||||
for (PersonalReportReviewItemRespVO item : merged) {
|
||||
String key = normalizeMergeText(item.getItemTitle());
|
||||
if (!StringUtils.hasText(key)) {
|
||||
continue;
|
||||
}
|
||||
PersonalReportReviewItemRespVO latestItem = latestItemMap.get(key);
|
||||
if (latestItem != null) {
|
||||
item.setWorkHours(latestItem.getWorkHours());
|
||||
item.setContentText(latestItem.getContentText());
|
||||
item.setContentJson(latestItem.getContentJson());
|
||||
}
|
||||
existingKeys.add(key);
|
||||
}
|
||||
for (PersonalReportReviewItemRespVO item : defaultList(latestItems)) {
|
||||
String key = normalizeMergeText(item.getItemTitle());
|
||||
if (StringUtils.hasText(key) && existingKeys.contains(key)) {
|
||||
continue;
|
||||
}
|
||||
merged.add(BeanUtils.toBean(item, PersonalReportReviewItemRespVO.class));
|
||||
if (StringUtils.hasText(key)) {
|
||||
existingKeys.add(key);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < merged.size(); i++) {
|
||||
merged.get(i).setItemNumber(i + 1);
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
private List<PersonalReportPlanItemRespVO> mergePlanItems(List<PersonalReportPlanItemReqVO> currentItems,
|
||||
List<PersonalReportPlanItemRespVO> latestItems) {
|
||||
List<PersonalReportPlanItemRespVO> merged = BeanUtils.toBean(defaultList(currentItems),
|
||||
|
||||
@@ -25,7 +25,7 @@ public class AuthLoginReqVO extends CaptchaVerificationReqVO {
|
||||
|
||||
@Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "buzhidao")
|
||||
@NotEmpty(message = "密码不能为空")
|
||||
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
|
||||
@Length(min = 4, max = 30, message = "密码长度为 4-30 位")
|
||||
private String password;
|
||||
|
||||
}
|
||||
|
||||
@@ -38,6 +38,6 @@ public class AuthRegisterReqVO extends CaptchaVerificationReqVO {
|
||||
|
||||
@Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
|
||||
@NotEmpty(message = "密码不能为空")
|
||||
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
|
||||
@Length(min = 4, max = 30, message = "密码长度为 4-30 位")
|
||||
private String password;
|
||||
}
|
||||
|
||||
@@ -12,12 +12,12 @@ public class UserProfileUpdatePasswordReqVO {
|
||||
|
||||
@Schema(description = "旧密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
|
||||
@NotEmpty(message = "旧密码不能为空")
|
||||
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
|
||||
@Length(min = 4, max = 30, message = "密码长度为 4-30 位")
|
||||
private String oldPassword;
|
||||
|
||||
@Schema(description = "新密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "654321")
|
||||
@NotEmpty(message = "新密码不能为空")
|
||||
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
|
||||
@Length(min = 4, max = 30, message = "密码长度为 4-30 位")
|
||||
private String newPassword;
|
||||
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ public class UserSaveReqVO {
|
||||
// ========== 仅【创建】时,需要传递的字段 ==========
|
||||
|
||||
@Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
|
||||
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
|
||||
@Length(min = 4, max = 30, message = "密码长度为 4-30 位")
|
||||
private String password;
|
||||
|
||||
@AssertTrue(message = "密码不能为空")
|
||||
|
||||
@@ -17,7 +17,7 @@ public class UserUpdatePasswordReqVO {
|
||||
|
||||
@Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
|
||||
@NotEmpty(message = "密码不能为空")
|
||||
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
|
||||
@Length(min = 4, max = 30, message = "密码长度为 4-30 位")
|
||||
private String password;
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user