Initial commit: backend code
This commit is contained in:
44
src/main/java/com/example/building/service/AuthService.java
Normal file
44
src/main/java/com/example/building/service/AuthService.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package com.example.building.service;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 认证服务接口
|
||||
*/
|
||||
public interface AuthService {
|
||||
|
||||
/**
|
||||
* 发送验证码
|
||||
*/
|
||||
void sendCode(String phone);
|
||||
|
||||
/**
|
||||
* 手机号验证码登录
|
||||
*/
|
||||
Map<String, Object> phoneLogin(String phone, String code);
|
||||
|
||||
/**
|
||||
* 微信扫码登录
|
||||
*/
|
||||
Map<String, Object> wechatLogin(String code);
|
||||
|
||||
/**
|
||||
* 支付宝扫码登录
|
||||
*/
|
||||
Map<String, Object> alipayLogin(String code);
|
||||
|
||||
/**
|
||||
* 刷新Token
|
||||
*/
|
||||
Map<String, Object> refreshToken(String refreshToken);
|
||||
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
*/
|
||||
Map<String, Object> getCurrentUser(String userId);
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
void logout(String token);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.example.building.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.example.building.entity.Customer;
|
||||
|
||||
/**
|
||||
* 客户服务接口
|
||||
*/
|
||||
public interface CustomerService {
|
||||
|
||||
/**
|
||||
* 客户列表
|
||||
*/
|
||||
Page<Customer> getCustomers(String keyword, Integer page, Integer pageSize);
|
||||
|
||||
/**
|
||||
* 客户详情
|
||||
*/
|
||||
Customer getCustomer(String id);
|
||||
|
||||
/**
|
||||
* 新增客户
|
||||
*/
|
||||
Customer createCustomer(Customer customer);
|
||||
|
||||
/**
|
||||
* 修改客户
|
||||
*/
|
||||
Customer updateCustomer(String id, Customer customer);
|
||||
|
||||
/**
|
||||
* 删除客户
|
||||
*/
|
||||
void deleteCustomer(String id);
|
||||
}
|
||||
48
src/main/java/com/example/building/service/OrderService.java
Normal file
48
src/main/java/com/example/building/service/OrderService.java
Normal file
@@ -0,0 +1,48 @@
|
||||
package com.example.building.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.example.building.dto.CreateOrderRequest;
|
||||
import com.example.building.entity.Order;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 订单服务接口
|
||||
* 核心业务:订单创建、价格计算(原价/优惠/实付)
|
||||
*/
|
||||
public interface OrderService {
|
||||
|
||||
/**
|
||||
* 创建订单
|
||||
* 核心逻辑:
|
||||
* 1. 计算订单原价(total_amount) = Σ(item.price × item.quantity)
|
||||
* 2. 计算优惠金额(discount_amount) = total_amount × (100 - discount_rate) / 100
|
||||
* 3. 计算实付金额(actual_amount) = total_amount - discount_amount
|
||||
*/
|
||||
Order createOrder(CreateOrderRequest request, String operatorId, String operatorName);
|
||||
|
||||
/**
|
||||
* 获取订单列表
|
||||
*/
|
||||
Page<Order> getOrders(String customerId, Integer status, String startDate, String endDate, Integer page, Integer pageSize);
|
||||
|
||||
/**
|
||||
* 获取订单详情(含明细)
|
||||
*/
|
||||
Map<String, Object> getOrderDetail(String orderId);
|
||||
|
||||
/**
|
||||
* 取消订单
|
||||
*/
|
||||
void cancelOrder(String orderId, String operatorId);
|
||||
|
||||
/**
|
||||
* 退款
|
||||
*/
|
||||
void refundOrder(String orderId, String operatorId);
|
||||
|
||||
/**
|
||||
* 订单统计
|
||||
*/
|
||||
Map<String, Object> getStatistics(String startDate, String endDate);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.example.building.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.example.building.entity.Category;
|
||||
import com.example.building.entity.Product;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 商品服务接口
|
||||
*/
|
||||
public interface ProductService {
|
||||
|
||||
/**
|
||||
* 获取分类列表
|
||||
*/
|
||||
List<Category> getCategories();
|
||||
|
||||
/**
|
||||
* 新增分类
|
||||
*/
|
||||
Category createCategory(Category category);
|
||||
|
||||
/**
|
||||
* 修改分类
|
||||
*/
|
||||
Category updateCategory(String id, Category category);
|
||||
|
||||
/**
|
||||
* 删除分类
|
||||
*/
|
||||
void deleteCategory(String id);
|
||||
|
||||
/**
|
||||
* 获取商品列表
|
||||
*/
|
||||
Page<Product> getProducts(String categoryId, String keyword, Integer page, Integer pageSize);
|
||||
|
||||
/**
|
||||
* 获取商品详情
|
||||
*/
|
||||
Product getProduct(String id);
|
||||
|
||||
/**
|
||||
* 新增商品
|
||||
*/
|
||||
Product createProduct(Product product);
|
||||
|
||||
/**
|
||||
* 修改商品
|
||||
*/
|
||||
Product updateProduct(String id, Product product);
|
||||
|
||||
/**
|
||||
* 删除商品
|
||||
*/
|
||||
void deleteProduct(String id);
|
||||
|
||||
/**
|
||||
* 获取库存预警商品
|
||||
*/
|
||||
List<Map<String, Object>> getStockAlerts();
|
||||
}
|
||||
40
src/main/java/com/example/building/service/StockService.java
Normal file
40
src/main/java/com/example/building/service/StockService.java
Normal file
@@ -0,0 +1,40 @@
|
||||
package com.example.building.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.example.building.entity.Stock;
|
||||
import com.example.building.entity.StockFlow;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 库存服务接口
|
||||
* 核心业务:入库、库存查询、库存流水
|
||||
*/
|
||||
public interface StockService {
|
||||
|
||||
/**
|
||||
* 入库
|
||||
*/
|
||||
Stock stockIn(String productId, Integer quantity, String remark, String operatorId);
|
||||
|
||||
/**
|
||||
* 库存查询
|
||||
*/
|
||||
Page<Stock> getStockList(String keyword, Integer page, Integer pageSize);
|
||||
|
||||
/**
|
||||
* 单商品库存
|
||||
*/
|
||||
Stock getStock(String productId);
|
||||
|
||||
/**
|
||||
* 库存调整
|
||||
*/
|
||||
Stock adjustStock(String productId, Integer quantity, String remark, String operatorId);
|
||||
|
||||
/**
|
||||
* 库存流水
|
||||
*/
|
||||
Page<StockFlow> getStockFlow(String productId, Integer type, Integer page, Integer pageSize);
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package com.example.building.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.example.building.common.JwtUtil;
|
||||
import com.example.building.entity.User;
|
||||
import com.example.building.mapper.UserMapper;
|
||||
import com.example.building.service.AuthService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 认证服务实现类
|
||||
* 支持:手机号验证码登录、微信扫码登录、支付宝扫码登录
|
||||
*/
|
||||
@Service
|
||||
public class AuthServiceImpl implements AuthService {
|
||||
|
||||
@Autowired
|
||||
private UserMapper userMapper;
|
||||
|
||||
@Autowired
|
||||
private JwtUtil jwtUtil;
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Value("${jwt.expiration:7200000}")
|
||||
private Long expiration;
|
||||
|
||||
/**
|
||||
* 发送验证码
|
||||
* 实际生产中应调用阿里云短信服务
|
||||
*/
|
||||
@Override
|
||||
public void sendCode(String phone) {
|
||||
// 生成6位随机验证码
|
||||
String code = String.format("%06d", (int) (Math.random() * 1000000));
|
||||
// 存入Redis,5分钟有效
|
||||
redisTemplate.opsForValue().set("sms:code:" + phone, code, 5, TimeUnit.MINUTES);
|
||||
// TODO: 调用短信服务发送验证码
|
||||
System.out.println("验证码已发送: " + phone + " - " + code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机号验证码登录
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> phoneLogin(String phone, String code) {
|
||||
// 验证验证码
|
||||
String savedCode = (String) redisTemplate.opsForValue().get("sms:code:" + phone);
|
||||
if (savedCode == null || !savedCode.equals(code)) {
|
||||
throw new RuntimeException("验证码错误或已过期");
|
||||
}
|
||||
|
||||
// 查询用户,不存在则创建
|
||||
User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
|
||||
.eq(User::getPhone, phone));
|
||||
if (user == null) {
|
||||
user = new User();
|
||||
user.setUserId(UUID.randomUUID().toString());
|
||||
user.setPhone(phone);
|
||||
user.setUsername("用户" + phone.substring(7));
|
||||
user.setRole("sales");
|
||||
user.setStatus(1);
|
||||
userMapper.insert(user);
|
||||
}
|
||||
|
||||
// 生成Token
|
||||
return generateTokens(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信扫码登录
|
||||
* 实际生产中需要调用微信API获取openid
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> wechatLogin(String code) {
|
||||
// TODO: 调用微信API获取openid
|
||||
// String openid = wechatService.getOpenId(code);
|
||||
String openid = "wechat_" + code;
|
||||
|
||||
// 查询用户,不存在则创建
|
||||
User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
|
||||
.eq(User::getWechatOpenid, openid));
|
||||
if (user == null) {
|
||||
user = new User();
|
||||
user.setUserId(UUID.randomUUID().toString());
|
||||
user.setWechatOpenid(openid);
|
||||
user.setUsername("微信用户");
|
||||
user.setRole("sales");
|
||||
user.setStatus(1);
|
||||
userMapper.insert(user);
|
||||
}
|
||||
|
||||
return generateTokens(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付宝扫码登录
|
||||
* 实际生产中需要调用支付宝API获取openid
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> alipayLogin(String code) {
|
||||
// TODO: 调用支付宝API获取openid
|
||||
// String openid = alipayService.getOpenId(code);
|
||||
String openid = "alipay_" + code;
|
||||
|
||||
// 查询用户,不存在则创建
|
||||
User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
|
||||
.eq(User::getAlipayOpenid, openid));
|
||||
if (user == null) {
|
||||
user = new User();
|
||||
user.setUserId(UUID.randomUUID().toString());
|
||||
user.setAlipayOpenid(openid);
|
||||
user.setUsername("支付宝用户");
|
||||
user.setRole("sales");
|
||||
user.setStatus(1);
|
||||
userMapper.insert(user);
|
||||
}
|
||||
|
||||
return generateTokens(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新Token
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> refreshToken(String refreshToken) {
|
||||
if (!jwtUtil.validateToken(refreshToken)) {
|
||||
throw new RuntimeException("刷新Token无效");
|
||||
}
|
||||
String userId = jwtUtil.getUserId(refreshToken);
|
||||
User user = userMapper.selectById(userId);
|
||||
if (user == null) {
|
||||
throw new RuntimeException("用户不存在");
|
||||
}
|
||||
return generateTokens(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> getCurrentUser(String userId) {
|
||||
User user = userMapper.selectById(userId);
|
||||
if (user == null) {
|
||||
throw new RuntimeException("用户不存在");
|
||||
}
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("userId", user.getUserId());
|
||||
result.put("username", user.getUsername());
|
||||
result.put("phone", user.getPhone());
|
||||
result.put("role", user.getRole());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
@Override
|
||||
public void logout(String token) {
|
||||
// 将token加入黑名单
|
||||
redisTemplate.opsForValue().set("blacklist:" + token, "1", 2, TimeUnit.HOURS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成Token和RefreshToken
|
||||
*/
|
||||
private Map<String, Object> generateTokens(User user) {
|
||||
String token = jwtUtil.generateToken(user.getUserId(), user.getUsername(), user.getRole());
|
||||
String refreshToken = jwtUtil.generateRefreshToken(user.getUserId());
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("token", token);
|
||||
result.put("refreshToken", refreshToken);
|
||||
result.put("userId", user.getUserId());
|
||||
result.put("username", user.getUsername());
|
||||
result.put("role", user.getRole());
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.example.building.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.example.building.entity.Customer;
|
||||
import com.example.building.mapper.CustomerMapper;
|
||||
import com.example.building.service.CustomerService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 客户服务实现类
|
||||
*/
|
||||
@Service
|
||||
public class CustomerServiceImpl implements CustomerService {
|
||||
|
||||
@Autowired
|
||||
private CustomerMapper customerMapper;
|
||||
|
||||
/**
|
||||
* 客户列表
|
||||
*/
|
||||
@Override
|
||||
public Page<Customer> getCustomers(String keyword, Integer page, Integer pageSize) {
|
||||
Page<Customer> pageParam = new Page<>(page, pageSize);
|
||||
LambdaQueryWrapper<Customer> wrapper = new LambdaQueryWrapper<>();
|
||||
if (StringUtils.hasText(keyword)) {
|
||||
wrapper.like(Customer::getName, keyword)
|
||||
.or()
|
||||
.like(Customer::getPhone, keyword);
|
||||
}
|
||||
wrapper.orderByDesc(Customer::getCreatedAt);
|
||||
return customerMapper.selectPage(pageParam, wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户详情
|
||||
*/
|
||||
@Override
|
||||
public Customer getCustomer(String id) {
|
||||
Customer customer = customerMapper.selectById(id);
|
||||
if (customer == null) {
|
||||
throw new RuntimeException("客户不存在");
|
||||
}
|
||||
return customer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增客户
|
||||
*/
|
||||
@Override
|
||||
public Customer createCustomer(Customer customer) {
|
||||
customer.setCustomerId(UUID.randomUUID().toString());
|
||||
customer.setTotalAmount(BigDecimal.ZERO);
|
||||
customerMapper.insert(customer);
|
||||
return customer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改客户
|
||||
*/
|
||||
@Override
|
||||
public Customer updateCustomer(String id, Customer customer) {
|
||||
Customer existing = customerMapper.selectById(id);
|
||||
if (existing == null) {
|
||||
throw new RuntimeException("客户不存在");
|
||||
}
|
||||
customer.setCustomerId(id);
|
||||
customerMapper.updateById(customer);
|
||||
return customer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除客户
|
||||
*/
|
||||
@Override
|
||||
public void deleteCustomer(String id) {
|
||||
customerMapper.deleteById(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,349 @@
|
||||
package com.example.building.service.impl;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.example.building.dto.CreateOrderRequest;
|
||||
import com.example.building.entity.*;
|
||||
import com.example.building.mapper.*;
|
||||
import com.example.building.service.OrderService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 订单服务实现类
|
||||
* 核心业务逻辑:
|
||||
* 1. 订单原价(total_amount) = Σ(item.price × item.quantity)
|
||||
* 2. 优惠金额(discount_amount) = total_amount × (100 - discount_rate) / 100
|
||||
* 3. 实付金额(actual_amount) = total_amount - discount_amount
|
||||
*/
|
||||
@Service
|
||||
public class OrderServiceImpl implements OrderService {
|
||||
|
||||
@Autowired
|
||||
private OrderMapper orderMapper;
|
||||
|
||||
@Autowired
|
||||
private OrderItemMapper orderItemMapper;
|
||||
|
||||
@Autowired
|
||||
private ProductMapper productMapper;
|
||||
|
||||
@Autowired
|
||||
private StockMapper stockMapper;
|
||||
|
||||
@Autowired
|
||||
private StockFlowMapper stockFlowMapper;
|
||||
|
||||
@Autowired
|
||||
private CustomerMapper customerMapper;
|
||||
|
||||
/**
|
||||
* 创建订单
|
||||
* 核心:价格计算、库存扣减、客户累计消费更新
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public Order createOrder(CreateOrderRequest request, String operatorId, String operatorName) {
|
||||
// 1. 准备订单数据
|
||||
Order order = new Order();
|
||||
order.setOrderId(UUID.randomUUID().toString());
|
||||
order.setOrderNo(generateOrderNo());
|
||||
order.setOperatorId(operatorId);
|
||||
order.setOperatorName(operatorName);
|
||||
order.setDiscountRate(request.getDiscountRate() != null ? request.getDiscountRate() : new BigDecimal("100"));
|
||||
order.setRemark(request.getRemark());
|
||||
order.setPaymentMethod(request.getPaymentMethod());
|
||||
order.setStatus(1); // 已完成
|
||||
|
||||
// 2. 查询客户信息(如果指定了客户)
|
||||
if (request.getCustomerId() != null) {
|
||||
Customer customer = customerMapper.selectById(request.getCustomerId());
|
||||
if (customer != null) {
|
||||
order.setCustomerId(customer.getCustomerId());
|
||||
order.setCustomerName(customer.getName());
|
||||
order.setCustomerPhone(customer.getPhone());
|
||||
order.setCustomerWechat(customer.getWechatOpenid());
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 计算订单金额
|
||||
BigDecimal totalAmount = BigDecimal.ZERO; // 原价
|
||||
List<OrderItem> orderItems = new ArrayList<>();
|
||||
|
||||
for (CreateOrderRequest.OrderItemDTO itemDTO : request.getItems()) {
|
||||
// 查询商品信息
|
||||
Product product = productMapper.selectById(itemDTO.getProductId());
|
||||
if (product == null) {
|
||||
throw new RuntimeException("商品不存在: " + itemDTO.getProductId());
|
||||
}
|
||||
|
||||
// 使用用户指定价格或商品标价
|
||||
BigDecimal price = itemDTO.getPrice() != null ? itemDTO.getPrice() : product.getPrice();
|
||||
|
||||
// 计算小计
|
||||
BigDecimal subtotal = price.multiply(new BigDecimal(itemDTO.getQuantity()))
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
|
||||
// 累加原价
|
||||
totalAmount = totalAmount.add(subtotal);
|
||||
|
||||
// 构建订单明细
|
||||
OrderItem item = new OrderItem();
|
||||
item.setItemId(UUID.randomUUID().toString());
|
||||
item.setOrderId(order.getOrderId());
|
||||
item.setProductId(product.getProductId());
|
||||
item.setProductName(product.getName());
|
||||
item.setProductSpec(product.getSpec());
|
||||
item.setUnit(product.getUnit());
|
||||
item.setPrice(price);
|
||||
item.setQuantity(itemDTO.getQuantity());
|
||||
item.setSubtotal(subtotal);
|
||||
orderItems.add(item);
|
||||
|
||||
// 4. 扣减库存
|
||||
decreaseStock(product.getProductId(), itemDTO.getQuantity(), order.getOrderId(), operatorId);
|
||||
}
|
||||
|
||||
// 设置订单原价
|
||||
order.setTotalAmount(totalAmount);
|
||||
|
||||
// 5. 计算优惠金额和实付金额
|
||||
BigDecimal discountRate = order.getDiscountRate();
|
||||
BigDecimal discountAmount = totalAmount.multiply(new BigDecimal("100").subtract(discountRate))
|
||||
.divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
|
||||
BigDecimal actualAmount = totalAmount.subtract(discountAmount);
|
||||
|
||||
order.setDiscountAmount(discountAmount);
|
||||
order.setActualAmount(actualAmount);
|
||||
|
||||
// 6. 保存订单
|
||||
orderMapper.insert(order);
|
||||
|
||||
// 7. 保存订单明细
|
||||
for (OrderItem item : orderItems) {
|
||||
orderItemMapper.insert(item);
|
||||
}
|
||||
|
||||
// 8. 更新客户累计消费金额
|
||||
if (request.getCustomerId() != null) {
|
||||
Customer customer = customerMapper.selectById(request.getCustomerId());
|
||||
if (customer != null) {
|
||||
BigDecimal newTotal = customer.getTotalAmount().add(actualAmount);
|
||||
customer.setTotalAmount(newTotal);
|
||||
customerMapper.updateById(customer);
|
||||
}
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取订单列表
|
||||
*/
|
||||
@Override
|
||||
public Page<Order> getOrders(String customerId, Integer status, String startDate, String endDate, Integer page, Integer pageSize) {
|
||||
Page<Order> pageParam = new Page<>(page, pageSize);
|
||||
LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
|
||||
if (customerId != null) {
|
||||
wrapper.eq(Order::getCustomerId, customerId);
|
||||
}
|
||||
if (status != null) {
|
||||
wrapper.eq(Order::getStatus, status);
|
||||
}
|
||||
if (startDate != null) {
|
||||
wrapper.ge(Order::getCreatedAt, startDate);
|
||||
}
|
||||
if (endDate != null) {
|
||||
wrapper.le(Order::getCreatedAt, endDate);
|
||||
}
|
||||
wrapper.orderByDesc(Order::getCreatedAt);
|
||||
return orderMapper.selectPage(pageParam, wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取订单详情(含明细)
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> getOrderDetail(String orderId) {
|
||||
Order order = orderMapper.selectById(orderId);
|
||||
if (order == null) {
|
||||
throw new RuntimeException("订单不存在");
|
||||
}
|
||||
|
||||
List<OrderItem> items = orderItemMapper.selectList(new LambdaQueryWrapper<OrderItem>()
|
||||
.eq(OrderItem::getOrderId, orderId));
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("order", order);
|
||||
result.put("items", items);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消订单
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public void cancelOrder(String orderId, String operatorId) {
|
||||
Order order = orderMapper.selectById(orderId);
|
||||
if (order == null) {
|
||||
throw new RuntimeException("订单不存在");
|
||||
}
|
||||
if (order.getStatus() != 1) {
|
||||
throw new RuntimeException("订单状态不允许取消");
|
||||
}
|
||||
|
||||
// 恢复库存
|
||||
List<OrderItem> items = orderItemMapper.selectList(new LambdaQueryWrapper<OrderItem>()
|
||||
.eq(OrderItem::getOrderId, orderId));
|
||||
for (OrderItem item : items) {
|
||||
increaseStock(item.getProductId(), item.getQuantity(), orderId, operatorId);
|
||||
}
|
||||
|
||||
// 更新订单状态
|
||||
order.setStatus(2); // 已取消
|
||||
orderMapper.updateById(order);
|
||||
}
|
||||
|
||||
/**
|
||||
* 退款
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public void refundOrder(String orderId, String operatorId) {
|
||||
Order order = orderMapper.selectById(orderId);
|
||||
if (order == null) {
|
||||
throw new RuntimeException("订单不存在");
|
||||
}
|
||||
if (order.getStatus() != 1) {
|
||||
throw new RuntimeException("订单状态不允许退款");
|
||||
}
|
||||
|
||||
// 恢复库存
|
||||
List<OrderItem> items = orderItemMapper.selectList(new LambdaQueryWrapper<OrderItem>()
|
||||
.eq(OrderItem::getOrderId, orderId));
|
||||
for (OrderItem item : items) {
|
||||
increaseStock(item.getProductId(), item.getQuantity(), orderId, operatorId);
|
||||
}
|
||||
|
||||
// 更新订单状态
|
||||
order.setStatus(4); // 已退款
|
||||
orderMapper.updateById(order);
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单统计
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> getStatistics(String startDate, String endDate) {
|
||||
LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(Order::getStatus, 1);
|
||||
if (startDate != null) {
|
||||
wrapper.ge(Order::getCreatedAt, startDate);
|
||||
}
|
||||
if (endDate != null) {
|
||||
wrapper.le(Order::getCreatedAt, endDate);
|
||||
}
|
||||
|
||||
List<Order> orders = orderMapper.selectList(wrapper);
|
||||
|
||||
BigDecimal totalAmount = BigDecimal.ZERO; // 原价合计
|
||||
BigDecimal actualAmount = BigDecimal.ZERO; // 实付合计
|
||||
|
||||
for (Order order : orders) {
|
||||
totalAmount = totalAmount.add(order.getTotalAmount());
|
||||
actualAmount = actualAmount.add(order.getActualAmount());
|
||||
}
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("orderCount", orders.size());
|
||||
result.put("totalAmount", totalAmount);
|
||||
result.put("actualAmount", actualAmount);
|
||||
result.put("discountAmount", totalAmount.subtract(actualAmount));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成订单编号
|
||||
* 规则: ORD + 年月日 + 6位序号
|
||||
*/
|
||||
private String generateOrderNo() {
|
||||
String date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||
String key = "order:no:" + date;
|
||||
// 这里简化处理,实际应使用Redis自增
|
||||
int seq = (int) (Math.random() * 1000000);
|
||||
return "ORD" + date + String.format("%06d", seq);
|
||||
}
|
||||
|
||||
/**
|
||||
* 扣减库存
|
||||
*/
|
||||
private void decreaseStock(String productId, Integer quantity, String relatedId, String operatorId) {
|
||||
Stock stock = stockMapper.selectOne(new LambdaQueryWrapper<Stock>()
|
||||
.eq(Stock::getProductId, productId));
|
||||
if (stock == null) {
|
||||
throw new RuntimeException("库存记录不存在");
|
||||
}
|
||||
if (stock.getQuantity() < quantity) {
|
||||
throw new RuntimeException("库存不足");
|
||||
}
|
||||
|
||||
int beforeQty = stock.getQuantity();
|
||||
stock.setQuantity(beforeQty - quantity);
|
||||
stockMapper.updateById(stock);
|
||||
|
||||
// 记录库存流水
|
||||
saveStockFlow(productId, 2, -quantity, beforeQty, beforeQty - quantity, relatedId, "sale", operatorId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加库存
|
||||
*/
|
||||
private void increaseStock(String productId, Integer quantity, String relatedId, String operatorId) {
|
||||
Stock stock = stockMapper.selectOne(new LambdaQueryWrapper<Stock>()
|
||||
.eq(Stock::getProductId, productId));
|
||||
if (stock == null) {
|
||||
stock = new Stock();
|
||||
stock.setStockId(UUID.randomUUID().toString());
|
||||
stock.setProductId(productId);
|
||||
stock.setQuantity(0);
|
||||
stock.setLockedQuantity(0);
|
||||
stockMapper.insert(stock);
|
||||
}
|
||||
|
||||
int beforeQty = stock.getQuantity();
|
||||
stock.setQuantity(beforeQty + quantity);
|
||||
stockMapper.updateById(stock);
|
||||
|
||||
// 记录库存流水
|
||||
saveStockFlow(productId, 1, quantity, beforeQty, beforeQty + quantity, relatedId, "cancel", operatorId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存库存流水
|
||||
*/
|
||||
private void saveStockFlow(String productId, Integer type, Integer quantity,
|
||||
Integer beforeQty, Integer afterQty, String relatedId,
|
||||
String relatedType, String operatorId) {
|
||||
StockFlow flow = new StockFlow();
|
||||
flow.setFlowId(UUID.randomUUID().toString());
|
||||
flow.setProductId(productId);
|
||||
flow.setType(type);
|
||||
flow.setQuantity(quantity);
|
||||
flow.setBeforeQuantity(beforeQty);
|
||||
flow.setAfterQuantity(afterQty);
|
||||
flow.setRelatedId(relatedId);
|
||||
flow.setRelatedType(relatedType);
|
||||
flow.setOperatorId(operatorId);
|
||||
stockFlowMapper.insert(flow);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package com.example.building.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.example.building.entity.Category;
|
||||
import com.example.building.entity.Product;
|
||||
import com.example.building.entity.Stock;
|
||||
import com.example.building.mapper.CategoryMapper;
|
||||
import com.example.building.mapper.ProductMapper;
|
||||
import com.example.building.mapper.StockMapper;
|
||||
import com.example.building.service.ProductService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 商品服务实现类
|
||||
* 支持商品CRUD和分类管理
|
||||
*/
|
||||
@Service
|
||||
public class ProductServiceImpl implements ProductService {
|
||||
|
||||
@Autowired
|
||||
private ProductMapper productMapper;
|
||||
|
||||
@Autowired
|
||||
private CategoryMapper categoryMapper;
|
||||
|
||||
@Autowired
|
||||
private StockMapper stockMapper;
|
||||
|
||||
/**
|
||||
* 获取分类列表
|
||||
*/
|
||||
@Override
|
||||
public List<Category> getCategories() {
|
||||
return categoryMapper.selectList(new LambdaQueryWrapper<Category>()
|
||||
.eq(Category::getStatus, 1)
|
||||
.orderByAsc(Category::getSortOrder));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增分类
|
||||
*/
|
||||
@Override
|
||||
public Category createCategory(Category category) {
|
||||
category.setCategoryId(UUID.randomUUID().toString());
|
||||
category.setStatus(1);
|
||||
categoryMapper.insert(category);
|
||||
return category;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改分类
|
||||
*/
|
||||
@Override
|
||||
public Category updateCategory(String id, Category category) {
|
||||
Category existing = categoryMapper.selectById(id);
|
||||
if (existing == null) {
|
||||
throw new RuntimeException("分类不存在");
|
||||
}
|
||||
category.setCategoryId(id);
|
||||
categoryMapper.updateById(category);
|
||||
return category;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除分类
|
||||
*/
|
||||
@Override
|
||||
public void deleteCategory(String id) {
|
||||
categoryMapper.deleteById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商品列表
|
||||
*/
|
||||
@Override
|
||||
public Page<Product> getProducts(String categoryId, String keyword, Integer page, Integer pageSize) {
|
||||
Page<Product> pageParam = new Page<>(page, pageSize);
|
||||
LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(Product::getStatus, 1);
|
||||
if (StringUtils.hasText(categoryId)) {
|
||||
wrapper.eq(Product::getCategoryId, categoryId);
|
||||
}
|
||||
if (StringUtils.hasText(keyword)) {
|
||||
wrapper.like(Product::getName, keyword);
|
||||
}
|
||||
wrapper.orderByDesc(Product::getCreatedAt);
|
||||
return productMapper.selectPage(pageParam, wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商品详情
|
||||
*/
|
||||
@Override
|
||||
public Product getProduct(String id) {
|
||||
Product product = productMapper.selectById(id);
|
||||
if (product == null || product.getStatus() == 0) {
|
||||
throw new RuntimeException("商品不存在");
|
||||
}
|
||||
return product;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增商品
|
||||
*/
|
||||
@Override
|
||||
public Product createProduct(Product product) {
|
||||
product.setProductId(UUID.randomUUID().toString());
|
||||
product.setStatus(1);
|
||||
productMapper.insert(product);
|
||||
return product;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改商品
|
||||
*/
|
||||
@Override
|
||||
public Product updateProduct(String id, Product product) {
|
||||
Product existing = productMapper.selectById(id);
|
||||
if (existing == null) {
|
||||
throw new RuntimeException("商品不存在");
|
||||
}
|
||||
product.setProductId(id);
|
||||
productMapper.updateById(product);
|
||||
return product;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除商品(软删)
|
||||
*/
|
||||
@Override
|
||||
public void deleteProduct(String id) {
|
||||
Product product = new Product();
|
||||
product.setProductId(id);
|
||||
product.setStatus(0);
|
||||
productMapper.updateById(product);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取库存预警商品
|
||||
* 查询库存低于预警值的商品
|
||||
*/
|
||||
@Override
|
||||
public List<Map<String, Object>> getStockAlerts() {
|
||||
// 查询所有商品及其库存
|
||||
List<Product> products = productMapper.selectList(new LambdaQueryWrapper<Product>()
|
||||
.eq(Product::getStatus, 1));
|
||||
|
||||
List<Map<String, Object>> alerts = new ArrayList<>();
|
||||
for (Product product : products) {
|
||||
Stock stock = stockMapper.selectOne(new LambdaQueryWrapper<Stock>()
|
||||
.eq(Stock::getProductId, product.getProductId()));
|
||||
int quantity = stock != null ? stock.getQuantity() : 0;
|
||||
if (quantity < product.getStockAlert()) {
|
||||
Map<String, Object> alert = new HashMap<>();
|
||||
alert.put("productId", product.getProductId());
|
||||
alert.put("productName", product.getName());
|
||||
alert.put("stockAlert", product.getStockAlert());
|
||||
alert.put("currentStock", quantity);
|
||||
alerts.add(alert);
|
||||
}
|
||||
}
|
||||
return alerts;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package com.example.building.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.example.building.entity.Product;
|
||||
import com.example.building.entity.Stock;
|
||||
import com.example.building.entity.StockFlow;
|
||||
import com.example.building.mapper.ProductMapper;
|
||||
import com.example.building.mapper.StockFlowMapper;
|
||||
import com.example.building.mapper.StockMapper;
|
||||
import com.example.building.service.StockService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 库存服务实现类
|
||||
* 核心业务:入库、库存查询、库存流水
|
||||
*/
|
||||
@Service
|
||||
public class StockServiceImpl implements StockService {
|
||||
|
||||
@Autowired
|
||||
private StockMapper stockMapper;
|
||||
|
||||
@Autowired
|
||||
private StockFlowMapper stockFlowMapper;
|
||||
|
||||
@Autowired
|
||||
private ProductMapper productMapper;
|
||||
|
||||
/**
|
||||
* 入库
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public Stock stockIn(String productId, Integer quantity, String remark, String operatorId) {
|
||||
// 验证商品存在
|
||||
Product product = productMapper.selectById(productId);
|
||||
if (product == null) {
|
||||
throw new RuntimeException("商品不存在");
|
||||
}
|
||||
|
||||
// 查询或创建库存记录
|
||||
Stock stock = stockMapper.selectOne(new LambdaQueryWrapper<Stock>()
|
||||
.eq(Stock::getProductId, productId));
|
||||
|
||||
int beforeQty = 0;
|
||||
if (stock == null) {
|
||||
stock = new Stock();
|
||||
stock.setStockId(UUID.randomUUID().toString());
|
||||
stock.setProductId(productId);
|
||||
stock.setQuantity(0);
|
||||
stock.setLockedQuantity(0);
|
||||
stockMapper.insert(stock);
|
||||
} else {
|
||||
beforeQty = stock.getQuantity();
|
||||
}
|
||||
|
||||
// 更新库存
|
||||
stock.setQuantity(beforeQty + quantity);
|
||||
stockMapper.updateById(stock);
|
||||
|
||||
// 记录库存流水 (类型1: 入库)
|
||||
saveStockFlow(productId, 1, quantity, beforeQty, beforeQty + quantity,
|
||||
null, "stock_in", operatorId, remark);
|
||||
|
||||
return stock;
|
||||
}
|
||||
|
||||
/**
|
||||
* 库存查询
|
||||
*/
|
||||
@Override
|
||||
public Page<Stock> getStockList(String keyword, Integer page, Integer pageSize) {
|
||||
Page<Stock> pageParam = new Page<>(page, pageSize);
|
||||
LambdaQueryWrapper<Stock> wrapper = new LambdaQueryWrapper<>();
|
||||
if (StringUtils.hasText(keyword)) {
|
||||
wrapper.like(Stock::getProductId, keyword);
|
||||
}
|
||||
wrapper.orderByDesc(Stock::getUpdatedAt);
|
||||
return stockMapper.selectPage(pageParam, wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 单商品库存
|
||||
*/
|
||||
@Override
|
||||
public Stock getStock(String productId) {
|
||||
Stock stock = stockMapper.selectOne(new LambdaQueryWrapper<Stock>()
|
||||
.eq(Stock::getProductId, productId));
|
||||
if (stock == null) {
|
||||
stock = new Stock();
|
||||
stock.setProductId(productId);
|
||||
stock.setQuantity(0);
|
||||
stock.setLockedQuantity(0);
|
||||
}
|
||||
return stock;
|
||||
}
|
||||
|
||||
/**
|
||||
* 库存调整
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public Stock adjustStock(String productId, Integer quantity, String remark, String operatorId) {
|
||||
// 验证商品存在
|
||||
Product product = productMapper.selectById(productId);
|
||||
if (product == null) {
|
||||
throw new RuntimeException("商品不存在");
|
||||
}
|
||||
|
||||
// 查询或创建库存记录
|
||||
Stock stock = stockMapper.selectOne(new LambdaQueryWrapper<Stock>()
|
||||
.eq(Stock::getProductId, productId));
|
||||
|
||||
int beforeQty = 0;
|
||||
if (stock == null) {
|
||||
stock = new Stock();
|
||||
stock.setStockId(UUID.randomUUID().toString());
|
||||
stock.setProductId(productId);
|
||||
stock.setQuantity(quantity);
|
||||
stock.setLockedQuantity(0);
|
||||
stockMapper.insert(stock);
|
||||
beforeQty = 0;
|
||||
} else {
|
||||
beforeQty = stock.getQuantity();
|
||||
stock.setQuantity(quantity);
|
||||
stockMapper.updateById(stock);
|
||||
}
|
||||
|
||||
// 记录库存流水 (类型3: 调整)
|
||||
int changeQty = quantity - beforeQty;
|
||||
saveStockFlow(productId, 3, changeQty, beforeQty, quantity,
|
||||
null, "adjust", operatorId, remark);
|
||||
|
||||
return stock;
|
||||
}
|
||||
|
||||
/**
|
||||
* 库存流水
|
||||
*/
|
||||
@Override
|
||||
public Page<StockFlow> getStockFlow(String productId, Integer type, Integer page, Integer pageSize) {
|
||||
Page<StockFlow> pageParam = new Page<>(page, pageSize);
|
||||
LambdaQueryWrapper<StockFlow> wrapper = new LambdaQueryWrapper<>();
|
||||
if (StringUtils.hasText(productId)) {
|
||||
wrapper.eq(StockFlow::getProductId, productId);
|
||||
}
|
||||
if (type != null) {
|
||||
wrapper.eq(StockFlow::getType, type);
|
||||
}
|
||||
wrapper.orderByDesc(StockFlow::getCreatedAt);
|
||||
return stockFlowMapper.selectPage(pageParam, wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存库存流水
|
||||
*/
|
||||
private void saveStockFlow(String productId, Integer type, Integer quantity,
|
||||
Integer beforeQty, Integer afterQty, String relatedId,
|
||||
String relatedType, String operatorId, String remark) {
|
||||
StockFlow flow = new StockFlow();
|
||||
flow.setFlowId(UUID.randomUUID().toString());
|
||||
flow.setProductId(productId);
|
||||
flow.setType(type);
|
||||
flow.setQuantity(quantity);
|
||||
flow.setBeforeQuantity(beforeQty);
|
||||
flow.setAfterQuantity(afterQty);
|
||||
flow.setRelatedId(relatedId);
|
||||
flow.setRelatedType(relatedType);
|
||||
flow.setOperatorId(operatorId);
|
||||
flow.setRemark(remark);
|
||||
stockFlowMapper.insert(flow);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user