fix: 第一版普遍修复结束。

This commit is contained in:
c
2026-03-21 15:56:13 +08:00
parent e142d099cc
commit aa216ffac0
38 changed files with 674 additions and 194 deletions

View File

@@ -0,0 +1,166 @@
package com.niuan.erp.common.aspect;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Controller 层日志切面
* 自动记录所有 Controller 接口的请求和响应信息
*/
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class ControllerLogAspect {
private final ObjectMapper objectMapper;
// 响应体大小限制2KB
private static final int MAX_RESPONSE_LENGTH = 2048;
// 忽略的静态资源后缀
private static final String[] IGNORE_SUFFIXES = {
".css", ".js", ".png", ".jpg", ".jpeg", ".gif", ".ico", ".svg",
".woff", ".woff2", ".ttf", ".eot", ".pdf", ".html", ".htm"
};
/**
* 切点:拦截所有 @RestController 或 @Controller 下的方法
*/
@Pointcut("@within(org.springframework.web.bind.annotation.RestController) || " +
"@within(org.springframework.stereotype.Controller)")
public void controllerPointcut() {
}
/**
* 环绕通知:记录请求和响应信息
*/
@Around("controllerPointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes == null) {
return joinPoint.proceed();
}
HttpServletRequest request = attributes.getRequest();
String uri = request.getRequestURI();
// 忽略静态资源请求
if (isStaticResource(uri)) {
return joinPoint.proceed();
}
long startTime = System.currentTimeMillis();
String method = request.getMethod();
String params = getRequestParams(joinPoint, request);
// 记录请求信息
log.info("[Request] {} {} | Params: {}", method, uri, params);
Object result = null;
int statusCode = 200;
try {
result = joinPoint.proceed();
return result;
} catch (Exception e) {
statusCode = 500;
// 异常信息记录到 error 日志,包含完整堆栈
log.error("[Exception] {} {} | Params: {} | Error: {}", method, uri, params, e.getMessage(), e);
throw e;
} finally {
long costTime = System.currentTimeMillis() - startTime;
String responseStr = formatResponse(result);
log.info("[Response] {} {} | Status: {} | Cost: {}ms | Result: {}",
method, uri, statusCode, costTime, responseStr);
}
}
/**
* 判断是否为静态资源请求
*/
private boolean isStaticResource(String uri) {
String lowerUri = uri.toLowerCase();
for (String suffix : IGNORE_SUFFIXES) {
if (lowerUri.endsWith(suffix)) {
return true;
}
}
return false;
}
/**
* 获取请求参数
*/
private String getRequestParams(ProceedingJoinPoint joinPoint, HttpServletRequest request) {
Map<String, Object> params = new HashMap<>();
// 获取 URL 参数
Map<String, String[]> parameterMap = request.getParameterMap();
if (!parameterMap.isEmpty()) {
Map<String, String> queryParams = new HashMap<>();
parameterMap.forEach((k, v) -> queryParams.put(k, String.join(",", v)));
params.put("query", queryParams);
}
// 获取 Body 参数(排除 HttpServletRequest、MultipartFile 等类型)
Object[] args = joinPoint.getArgs();
if (args != null && args.length > 0) {
Object bodyParam = Arrays.stream(args)
.filter(arg -> arg != null
&& !(arg instanceof HttpServletRequest)
&& !(arg instanceof MultipartFile)
&& !(arg instanceof MultipartFile[]))
.findFirst()
.orElse(null);
if (bodyParam != null) {
params.put("body", bodyParam);
}
}
try {
return objectMapper.writeValueAsString(params);
} catch (Exception e) {
return params.toString();
}
}
/**
* 格式化响应内容,限制大小
*/
private String formatResponse(Object result) {
if (result == null) {
return "null";
}
try {
String json = objectMapper.writeValueAsString(result);
if (json.length() > MAX_RESPONSE_LENGTH) {
return json.substring(0, MAX_RESPONSE_LENGTH) + "... [Response too large, total: " + json.length() + " chars]";
}
return json;
} catch (Exception e) {
String str = result.toString();
if (str.length() > MAX_RESPONSE_LENGTH) {
return str.substring(0, MAX_RESPONSE_LENGTH) + "... [Response too large, total: " + str.length() + " chars]";
}
return str;
}
}
}

View File

@@ -4,12 +4,21 @@ import jakarta.validation.Validator;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
@Configuration
public class ValidationConfig {
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:i18n/messages");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
@Bean
public LocalValidatorFactoryBean validator(MessageSource messageSource) {
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
@@ -23,5 +32,4 @@ public class ValidationConfig {
processor.setValidator(validator);
return processor;
}
}

View File

@@ -2,6 +2,8 @@ package com.niuan.erp.common.handler;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.niuan.erp.common.utils.SecurityUtils;
import com.niuan.erp.module.sys.entity.SysUser;
import com.niuan.erp.module.sys.enums.UserType;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
@@ -39,6 +41,7 @@ public class CustomerTenantHandler implements TenantLineHandler {
@Override
public boolean ignoreTable(String tableName) {
return !customerTables.contains(tableName);
return !customerTables.contains(tableName) ||
SecurityUtils.getLoginUser().getUser().getUserType().equals(UserType.SYSTEM_USER);
}
}

View File

@@ -14,7 +14,6 @@ public class SecurityUtils {
throw new BusinessException("auth.unLogin");
}
Object principal = authentication.getPrincipal();
System.out.println(principal);
if (principal instanceof LoginUser user) {
return user;
}

View File

@@ -1,10 +1,18 @@
package com.niuan.erp.module.auth.controller;
import com.niuan.erp.common.base.BaseResult;
import com.niuan.erp.common.base.LoginUser;
import com.niuan.erp.common.security.JwtTokenProvider;
import com.niuan.erp.common.security.TokenService;
import com.niuan.erp.common.utils.SecurityUtils;
import com.niuan.erp.module.auth.service.SystemAuthService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -17,6 +25,12 @@ public class SystemAuthController {
private final SystemAuthService systemAuthService;
private final JwtTokenProvider jwtTokenProvider;
private final TokenService tokenService;
private static final String TOKEN_PREFIX = "Bearer ";
/**
* 后台权限接口
* @return
@@ -27,4 +41,78 @@ public class SystemAuthController {
return BaseResult.successWithData(systemAuthService.getRouterConfigRawList());
}
/**
* 检测用户登录状态
* 支持两种检测方式:
* 1. Session方式Web端检查Spring Security上下文
* 2. JWT方式小程序检查Authorization头中的Token
*/
@GetMapping("/check-login")
@Operation(summary = "检测登录状态", description = "检测当前是否已登录支持Session和JWT两种方式")
public BaseResult<LoginStatusResponse> checkLogin(HttpServletRequest request) {
// 方式1检查SessionWeb端
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()
&& authentication.getPrincipal() instanceof LoginUser) {
LoginUser user = (LoginUser) authentication.getPrincipal();
LoginStatusResponse response = new LoginStatusResponse(
true,
new UserInfo(user.getUser().getId(), user.getUser().getLoginName(), user.getUser().getUserName()),
"登录状态有效Session"
);
return BaseResult.successWithData(response);
}
// 方式2检查JWT小程序端
String token = extractToken(request);
if (StringUtils.hasText(token)) {
// 检查token是否在黑名单中
if (tokenService.isTokenBlacklisted(token)) {
return BaseResult.successWithData(new LoginStatusResponse(false, null, "Token已失效"));
}
// 验证token有效性
if (jwtTokenProvider.validateToken(token)) {
// 获取用户信息
Long userId = jwtTokenProvider.getUserIdFromToken(token);
String loginName = jwtTokenProvider.getLoginNameFromToken(token);
LoginStatusResponse response = new LoginStatusResponse(
true,
new UserInfo(userId, loginName, null),
"登录状态有效JWT"
);
return BaseResult.successWithData(response);
} else {
return BaseResult.successWithData(new LoginStatusResponse(false, null, "Token无效或已过期"));
}
}
// 未登录
return BaseResult.successWithData(new LoginStatusResponse(false, null, "未登录"));
}
/**
* 从请求中提取token
*/
private String extractToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(TOKEN_PREFIX)) {
return bearerToken.substring(TOKEN_PREFIX.length());
}
return null;
}
/**
* 登录状态响应
*/
public record LoginStatusResponse(boolean isLoggedIn, UserInfo userInfo, String message) {
}
/**
* 用户信息
*/
public record UserInfo(Long userId, String loginName, String username) {
}
}

View File

@@ -16,6 +16,6 @@ import java.util.List;
*/
public interface DocumentMaterialMapper extends BaseMapper<DocumentMaterial> {
List<DocumentMaterialWithInfo> selectByPartNumber(List<Long> documentIds, String partNumber);
List<DocumentMaterialWithInfo> selectListByPartNumber(List<Long> documentIds, String partNumber);
}

View File

@@ -1,108 +0,0 @@
package com.niuan.erp.module.open.controller;
import com.niuan.erp.common.base.BaseResult;
import com.niuan.erp.common.base.LoginUser;
import com.niuan.erp.common.security.JwtTokenProvider;
import com.niuan.erp.common.security.TokenService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 公开接口控制器
* 用于前端检测登录状态等不需要认证的接口
* 同时支持Session和JWT两种认证方式
*/
@RestController
@RequestMapping("/open")
@RequiredArgsConstructor
@Tag(name = "公开接口", description = "不需要认证的公开接口")
public class OpenController {
private final JwtTokenProvider jwtTokenProvider;
private final TokenService tokenService;
private static final String TOKEN_PREFIX = "Bearer ";
/**
* 检测用户登录状态
* 支持两种检测方式:
* 1. Session方式Web端检查Spring Security上下文
* 2. JWT方式小程序检查Authorization头中的Token
*/
@GetMapping("/check-login")
@Operation(summary = "检测登录状态", description = "检测当前是否已登录支持Session和JWT两种方式")
public BaseResult<LoginStatusResponse> checkLogin(HttpServletRequest request) {
// 方式1检查SessionWeb端
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()
&& authentication.getPrincipal() instanceof LoginUser) {
LoginUser user = (LoginUser) authentication.getPrincipal();
LoginStatusResponse response = new LoginStatusResponse(
true,
new UserInfo(user.getUser().getId(), user.getUser().getLoginName(), user.getUser().getUserName()),
"登录状态有效Session"
);
return BaseResult.successWithData(response);
}
// 方式2检查JWT小程序端
String token = extractToken(request);
if (StringUtils.hasText(token)) {
// 检查token是否在黑名单中
if (tokenService.isTokenBlacklisted(token)) {
return BaseResult.successWithData(new LoginStatusResponse(false, null, "Token已失效"));
}
// 验证token有效性
if (jwtTokenProvider.validateToken(token)) {
// 获取用户信息
Long userId = jwtTokenProvider.getUserIdFromToken(token);
String loginName = jwtTokenProvider.getLoginNameFromToken(token);
LoginStatusResponse response = new LoginStatusResponse(
true,
new UserInfo(userId, loginName, null),
"登录状态有效JWT"
);
return BaseResult.successWithData(response);
} else {
return BaseResult.successWithData(new LoginStatusResponse(false, null, "Token无效或已过期"));
}
}
// 未登录
return BaseResult.successWithData(new LoginStatusResponse(false, null, "未登录"));
}
/**
* 从请求中提取token
*/
private String extractToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(TOKEN_PREFIX)) {
return bearerToken.substring(TOKEN_PREFIX.length());
}
return null;
}
/**
* 登录状态响应
*/
public record LoginStatusResponse(boolean isLoggedIn, UserInfo userInfo, String message) {
}
/**
* 用户信息
*/
public record UserInfo(Long userId, String loginName, String userName) {
}
}

View File

@@ -4,10 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.niuan.erp.common.annotation.ApiLog;
import com.niuan.erp.common.annotation.ModuleLog;
import com.niuan.erp.common.base.BasePageReqParams;
import com.niuan.erp.common.base.BaseResult;
import com.niuan.erp.common.base.BaseSelectDto;
import com.niuan.erp.common.base.OperationType;
import com.niuan.erp.common.base.*;
import com.niuan.erp.module.production.controller.dto.BomAddDto;
import com.niuan.erp.module.production.controller.dto.BomDto;
import com.niuan.erp.module.production.controller.dto.BomItemDto;
@@ -77,4 +74,13 @@ public class BomController {
bomService.addBom(dto);
return BaseResult.success();
}
@ApiLog(type = OperationType.DELETE, remark = "删除一条Bom记录")
@Operation(summary = "删除Bom", operationId = "deleteBom")
@PostMapping("/deleteBom")
@PreAuthorize("hasAuthority('bom:delete')")
public BaseResult<?> deleteBom(@Validated(CommonValidateGroup.DeleteOne.class) @RequestBody BaseDeleteBody req) {
bomService.deleteBom(req.id());
return BaseResult.success();
}
}

View File

@@ -45,6 +45,7 @@ public class ProductionReturnController {
.or()
.like(Document::getFormMark, searchCode));
}
wrapper.orderByDesc(Document::getCreateDate);
return BaseResult.successWithData(productionReturnService.getProductionReturnPage(pageParams, wrapper));
}

View File

@@ -50,14 +50,14 @@ public class BomServiceImpl extends ServiceImpl<BomMapper, Bom> implements BomSe
}
@Override
public IPage<BomDto> getBomPageWithItem(BasePageReqParams pageParams, String searchCode, String partNumber) {
IPage<Bom> bomPage = this.baseMapper.selectPageByPartNumber(new Page<>(pageParams.page(), pageParams.pageSize()),
searchCode, partNumber);
List<Bom> bomList = bomPage.getRecords();
bomList.forEach(b -> {
b.setBomItems(bomItemMapper.getBomItemListByBomIdAndPartNumber(b.getId(), partNumber));
});
return bomPage.convert(bomConverter::toDto);
public IPage<BomDto> getBomPageWithItem(BasePageReqParams pageParams, String searchCode, String partNumber) {
IPage<Bom> bomPage = this.baseMapper.selectPageByPartNumber(new Page<>(pageParams.page(), pageParams.pageSize()),
searchCode, partNumber);
List<Bom> bomList = bomPage.getRecords();
bomList.forEach(b -> {
b.setBomItems(bomItemMapper.getBomItemListByBomIdAndPartNumber(b.getId(), partNumber));
});
return bomPage.convert(bomConverter::toDto);
}
@Override

View File

@@ -38,12 +38,9 @@ public class PurchaseOrderController {
@PreAuthorize("hasAuthority('purchase_order:index')")
public BaseResult<IPage<PurchaseOrderDto>> getPurchaseOrderPage(
@Validated BasePageReqParams pageParams,
@Parameter(description = "单据编号/供应商名称") @RequestParam(required = false) String searchCode) {
var wrapper = new LambdaQueryWrapper<Document>();
if (StringUtils.hasText(searchCode)) {
wrapper.like(Document::getFormCode, searchCode).or().like(Document::getVendorName, searchCode);
}
return BaseResult.successWithData(purchaseOrderService.getPurchaseOrderPage(pageParams, wrapper));
@Parameter(description = "单据编号/供应商名称") @RequestParam(required = false) String searchCode,
@Parameter(description = "物料编号") @RequestParam(required = false) String partNumber) {
return BaseResult.successWithData(purchaseOrderService.getPurchaseOrderPage(pageParams, searchCode, partNumber));
}
@Operation(summary = "查询采购订单明细数据", operationId = "getPurchaseOrderItems")
@@ -67,7 +64,7 @@ public class PurchaseOrderController {
@ApiLog(type = OperationType.UPDATE, remark = "更新一条采购订单记录")
@Operation(summary = "更新采购订单", operationId = "updatePurchaseOrder")
@PostMapping("/updatePurchaseOrder")
@PreAuthorize("hasAuthority('purchase_order:update')")
@PreAuthorize("hasAuthority('purchase_order:edit')")
public BaseResult<?> updatePurchaseOrder(@Validated @RequestBody PurchaseOrderAddDto dto) {
purchaseOrderService.updatePurchaseOrder(dto);
return BaseResult.success();

View File

@@ -10,11 +10,13 @@ import java.util.List;
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface PurchaseOrderConverter {
Document toEntity(PurchaseOrderDto dto);
@Mapping(target = "planId", expression = "java(entity.getGroupId() != null ? entity.getGroupId().longValue() : null)")
@Mapping(target = "vendorNo", source = "vendorNo")
@Mapping(target = "vendorName", source = "vendorName")
PurchaseOrderDto toDto(Document entity);
List<PurchaseOrderDto> toDtoList(List<Document> entities);
}

View File

@@ -1,16 +1,26 @@
package com.niuan.erp.module.purchase.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.niuan.erp.module.purchase.controller.dto.PurchaseOrderItemDto;
import com.niuan.erp.module.purchase.entity.PurchaseOrderItem;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* <p>
* Mapper 接口
* </p>
*
* @author
* @author
* @since 2026-02-11
*/
public interface PurchaseOrderItemMapper extends BaseMapper<PurchaseOrderItem> {
@Select("SELECT DISTINCT DocumentNo FROM purchasematerial WHERE PartNumber LIKE CONCAT('%', #{partNumber}, '%')")
List<Integer> selectOrderIdsByPartNumber(@Param("partNumber") String partNumber);
List<PurchaseOrderItemDto> selectListByOrderIdsAndPartNumber(@Param("orderIds") List<Long> orderIds, @Param("partNumber") String partNumber);
}

View File

@@ -10,7 +10,7 @@ import java.util.List;
public interface PurchaseOrderService {
IPage<PurchaseOrderDto> getPurchaseOrderPage(BasePageReqParams pageParams, LambdaQueryWrapper<Document> wrapper);
IPage<PurchaseOrderDto> getPurchaseOrderPage(BasePageReqParams pageParams, String searchCode, String partNumber);
void addPurchaseOrder(PurchaseOrderAddDto dto);

View File

@@ -30,6 +30,7 @@ import com.niuan.erp.module.warehouse.mapper.WarehouseItemMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@@ -52,10 +53,58 @@ public class PurchaseOrderServiceImpl extends ServiceImpl<DocumentMapper, Docume
private final DocumentMaterialMapper documentMaterialMapper;
@Override
public IPage<PurchaseOrderDto> getPurchaseOrderPage(BasePageReqParams pageParams, LambdaQueryWrapper<Document> wrapper) {
public IPage<PurchaseOrderDto> getPurchaseOrderPage(BasePageReqParams pageParams, String searchCode, String partNumber) {
var wrapper = new LambdaQueryWrapper<Document>();
wrapper.eq(Document::getFormType, DocumentType.PURCHASE_ORDER);
if (StringUtils.hasText(searchCode)) {
wrapper.like(Document::getFormCode, searchCode).or().like(Document::getVendorName, searchCode);
}
if (StringUtils.hasText(partNumber)) {
List<Integer> orderIds = purchaseOrderItemMapper.selectOrderIdsByPartNumber(partNumber);
if (!orderIds.isEmpty()) {
wrapper.in(Document::getId, orderIds);
} else {
wrapper.eq(Document::getId, -1);
}
}
wrapper.orderByDesc(Document::getCreateDate);
IPage<Document> result = this.baseMapper.selectPage(new Page<>(pageParams.page(), pageParams.pageSize()), wrapper);
// 如果有过滤条件,查询并填充明细
if (StringUtils.hasText(partNumber) && !result.getRecords().isEmpty()) {
List<Long> parentIds = result.getRecords().stream()
.map(Document::getId)
.toList();
List<PurchaseOrderItemDto> allItems = purchaseOrderItemMapper.selectListByOrderIdsAndPartNumber(parentIds, partNumber);
return result.convert(entity -> {
PurchaseOrderDto dto = purchaseOrderConverter.toDto(entity);
Long entityId = entity.getId();
List<PurchaseOrderItemDto> items = allItems.stream()
.filter(item -> item.documentNo() != null && item.documentNo().longValue() == entityId.longValue())
.toList();
return new PurchaseOrderDto(
dto.id(),
dto.formCode(),
dto.formName(),
dto.vendorName(),
dto.vendorNo(),
dto.totalValue(),
dto.formMark(),
dto.formStatus(),
dto.createDate(),
dto.createUserName(),
dto.storeNo(),
dto.storeName(),
dto.planId(),
items
);
});
}
return result.convert(purchaseOrderConverter::toDto);
}

View File

@@ -18,6 +18,14 @@ public record DeviceDto(
String softVersion,
@Schema(description = "算法版本")
String alVersion,
@Schema(description = "算法标志")
String alNum,
@Schema(description = "激活状态")
Boolean alStatus,
@Schema(description = "生产日期")
LocalDateTime createDate,
@Schema(description = "备注")
String mark,
@Schema(description = "出库日期/出货日期")
LocalDateTime outProductDate,
@Schema(description = "返修记录拼接字符串")

View File

@@ -32,6 +32,13 @@ public record SaleOrderAddDto(
@NotBlank(message = "sale.sale_order.validate.customer_name.not_null")
String customerName,
@Schema(description = "出货仓库ID")
@NotNull(message = "sale.sale_order.validate.store_no.not_null")
Integer storeNo,
@Schema(description = "出货仓库名称")
String storeName,
@Schema(description = "销售订单明细列表")
@NotEmpty(message = "sale.sale_order.validate.sale_order_items.not_null")
@Valid

View File

@@ -18,6 +18,8 @@ public record SaleOrderDto(
Integer formStatus,
Integer customerId,
String customerName,
Integer storeNo,
String storeName,
Double totalValue,
String searchCode
) {}

View File

@@ -26,6 +26,11 @@ public record SaleOrderUpdateDto(
@NotBlank(message = "sale.sale_order.validate.customer_name.not_null", groups = CommonValidateGroup.Update.class)
String customerName,
@NotNull(message = "sale.sale_order.validate.store_no.not_null", groups = CommonValidateGroup.Update.class)
Integer storeNo,
String storeName,
@NotEmpty(message = "sale.sale_order.validate.sale_order_items.not_null", groups = CommonValidateGroup.Update.class)
@Valid
List<SaleOrderItemUpdateDto> saleOrderItems

View File

@@ -22,6 +22,8 @@ public interface SaleOrderConverter {
@Mapping(source = "formStatus.value", target = "formStatus")
@Mapping(source = "customerId", target = "customerId")
@Mapping(source = "storeName", target = "customerName")
@Mapping(source = "storeNo", target = "storeNo")
@Mapping(source = "outStoreName", target = "storeName")
@Mapping(source = "totalValue", target = "totalValue")
SaleOrderDto toDto(Document entity);

View File

@@ -77,6 +77,8 @@ public class SaleOrderServiceImpl extends ServiceImpl<DocumentMapper, Document>
entity.setFormMark(dto.formMark());
entity.setCustomerId(dto.customerId());
entity.setStoreName(dto.customerName());
entity.setStoreNo(dto.storeNo());
entity.setOutStoreName(dto.storeName());
entity.setTotalValue(totalValue);
entity.setFormType(DocumentType.SALES_ORDER);
entity.setFormStatus(FormStatus.NO_APPROVE);
@@ -129,6 +131,8 @@ public class SaleOrderServiceImpl extends ServiceImpl<DocumentMapper, Document>
existingEntity.setFormMark(dto.formMark());
existingEntity.setCustomerId(dto.customerId());
existingEntity.setStoreName(dto.customerName());
existingEntity.setStoreNo(dto.storeNo());
existingEntity.setOutStoreName(dto.storeName());
existingEntity.setTotalValue(totalValue);
existingEntity.setUpdateUserId(SecurityUtils.getUserId());
existingEntity.setUpdateUserName(SecurityUtils.getUserName());
@@ -247,11 +251,11 @@ public class SaleOrderServiceImpl extends ServiceImpl<DocumentMapper, Document>
}
if (stock == null) {
throw new BusinessException("warehouse.stock_transfer_order.exception.stock_not_found");
throw new BusinessException("sale.sale_order.exception.stock_not_found", item.getPartNumber());
}
if (stock.getProductCount() < item.getSaleCount()) {
throw new BusinessException("warehouse.stock_transfer_order.exception.insufficient_stock");
throw new BusinessException("sale.sale_order.exception.insufficient_stock", item.getPartNumber());
}
// 扣除库存 - 如果有多条记录,只更新第一条

View File

@@ -2,7 +2,9 @@ package com.niuan.erp.module.sys.converter;
import com.niuan.erp.module.sys.controller.dto.SysUserDto;
import com.niuan.erp.module.sys.entity.SysUser;
import com.niuan.erp.module.sys.enums.UserType;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ReportingPolicy;
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
@@ -12,4 +14,11 @@ public interface SysUserConverter {
SysUser toEntity(SysUserDto dto);
default Integer mapUserTypeToInt(UserType userType) {
return userType.getValue();
}
default UserType mapUserTypeToUserType(Integer userType) {
return UserType.fromCode(userType);
}
}

View File

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.niuan.erp.module.sys.enums.UserType;
import lombok.Data;
import java.time.LocalDateTime;
@@ -37,7 +38,7 @@ public class SysUser {
private String updateUserName;
@TableField("UserType")
private Long userType;
private UserType userType;
@TableField("LoginName")
private String loginName;

View File

@@ -0,0 +1,27 @@
package com.niuan.erp.module.sys.enums;
import com.baomidou.mybatisplus.annotation.IEnum;
public enum UserType implements IEnum<Integer> {
SYSTEM_USER(5, "系统管理员"),
COMMON_USER(0, "普通用户");
final int code;
final String description;
UserType(int code, String description) {
this.code = code;
this.description = description;
}
public static UserType fromCode(int code) {
for (UserType userType : UserType.values()) {
if (userType.code == code) return userType;
}
// TODO 异常
return null;
}
@Override
public Integer getValue() {
return code;
}
}

View File

@@ -14,7 +14,7 @@ public interface SysUserMapper extends BaseMapper<SysUser> {
/**
* 根据角色ID查询用户ID列表
*/
@Select("SELECT DISTINCT ur.UserId FROM yy_usersrolemapping ur WHERE ur.RoleId = #{roleId}")
@Select("SELECT DISTINCT ur.UserId FROM role_permission ur WHERE ur.role_id = #{roleId}")
List<Long> selectUserIdsByRoleId(@Param("roleId") Long roleId);
/**
@@ -23,8 +23,8 @@ public interface SysUserMapper extends BaseMapper<SysUser> {
@Select("""
SELECT DISTINCT ur.UserId
FROM yy_usersrolemapping ur
INNER JOIN yy_rolepermission rp ON ur.RoleId = rp.RoleId
WHERE rp.PermissionId = #{permissionId}
INNER JOIN role_permission rp ON ur.RoleId = rp.role_id
WHERE rp.permission_id = #{permissionId}
""")
List<Long> selectUserIdsByPermissionId(@Param("permissionId") Long permissionId);
}

View File

@@ -45,15 +45,19 @@ public class KeyAccountServiceImpl extends ServiceImpl<KeyAccountMapper, KeyAcco
@Override
public List<BaseTree> getKeyAccountTree() {
LambdaQueryWrapper<KeyAccount> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(KeyAccount::getStatus, 0);
wrapper.orderByAsc(KeyAccount::getLevelPath);
List<KeyAccount> keyAccounts = this.baseMapper.selectList(wrapper);
// LambdaQueryWrapper<KeyAccount> wrapper = new LambdaQueryWrapper<>();
// wrapper.eq(KeyAccount::getStatus, 0);
// wrapper.orderByAsc(KeyAccount::getLevelPath);
// List<KeyAccount> keyAccounts = this.baseMapper.selectList(wrapper);
Map<Long, List<KeyAccount>> parentMap = keyAccounts.stream()
.collect(Collectors.groupingBy(ka -> ka.getParentId() != null ? ka.getParentId() : 0L));
// Map<Long, List<KeyAccount>> parentMap = keyAccounts.stream()
// .collect(Collectors.groupingBy(ka -> ka.getParentId() != null ? ka.getParentId() : 0L));
return buildTree(parentMap, 0L);
return this.baseMapper.getKeyAccountSelectList()
.stream()
.map(ka -> new BaseTree(ka.value(), ka.label(), null))
.toList();
// return buildTree(parentMap, 0L);
}
private List<BaseTree> buildTree(Map<Long, List<KeyAccount>> parentMap, Long parentId) {

View File

@@ -2,6 +2,7 @@ package com.niuan.erp.module.sys.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.niuan.erp.common.base.BasePageReqParams;
@@ -148,6 +149,21 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
// 密码加密
entity.setPassWord(passwordEncoder.encode(dto.passWord()));
// 得到 CustomerId
List<Integer> customerIdList = this.baseMapper.selectObjs(
Wrappers.<SysUser>lambdaQuery()
.select(SysUser::getCustomerId)
.orderByDesc(SysUser::getCustomerId)
.last("LIMIT 1")
);
entity.setIsCustomer(1);
if (customerIdList == null || customerIdList.isEmpty()) {
entity.setCustomerId(0);
} else {
entity.setCustomerId(customerIdList.getFirst() + 1);
}
this.baseMapper.insert(entity);
// 保存用户角色关联

View File

@@ -64,7 +64,7 @@ public class WarehouseReceiptController {
@ApiLog(type = OperationType.UPDATE, remark = "更新一条WarehouseReceipt记录")
@Operation(summary = "更新WarehouseReceipt", operationId = "updateWarehouseReceipt")
@PostMapping("/updateWarehouseReceipt")
@PreAuthorize("hasAuthority('warehouse_receipt:update')")
@PreAuthorize("hasAuthority('warehouse_receipt:edit')")
public BaseResult<?> updateWarehouseReceipt(@Validated(Update.class) @RequestBody WarehouseReceiptDto dto) {
warehouseReceiptService.updateWarehouseReceipt(dto);
return BaseResult.success();

View File

@@ -68,7 +68,7 @@ public class StockTransferOrderServiceImpl extends ServiceImpl<DocumentMapper, D
searchParams.searchCode(),
searchParams.partNumber(),
DocumentType.STOCK_TRANSFER_ORDER);
var items = documentMaterialMapper.selectByPartNumber(
var items = documentMaterialMapper.selectListByPartNumber(
orderPage.getRecords().stream().map(Document::getId).toList(), searchParams.partNumber());
return orderPage.convert(order -> new StockTransferOrderDto(
order.getId(),
@@ -419,7 +419,7 @@ public class StockTransferOrderServiceImpl extends ServiceImpl<DocumentMapper, D
@Override
public List<StockTransferOrderItemDto> getItem(long id) {
var items = documentMaterialMapper.selectByPartNumber(List.of(id), null);
var items = documentMaterialMapper.selectListByPartNumber(List.of(id), null);
return items.stream().map(stockTransferOrderConverter::toItemDto).toList();
}