项目概述
企业销售管理系统是一个基于Java全栈技术开发的综合性管理平台,旨在帮助企业高效管理销售流程、客户关系和业务数据。本文将详细介绍该系统的技术架构、核心功能以及实现要点。
技术栈
后端技术
- Spring Boot 2.7.x:核心框架
- Spring Security:认证和授权
- MyBatis Plus:ORM框架
- MySQL 8.0:数据库
- Redis:缓存处理
- JWT:用户令牌管理
- Maven:项目管理工具
前端技术
- Vue 3:前端框架
- Element Plus:UI组件库
- Axios:HTTP客户端
- Vuex:状态管理
- Vue Router:路由管理
- ECharts:数据可视化
核心功能模块
1. 用户权限管理
- 基于RBAC的权限控制
- 多角色管理
- 动态权限分配
- 操作日志记录
2. 客户管理
- 客户信息维护
- 客户分类管理
- 客户跟进记录
- 客户画像分析
3. 销售管理
- 销售订单处理
- 报价管理
- 合同管理
- 发票管理
4. 产品管理
- 产品信息维护
- 产品分类管理
- 价格策略管理
- 库存管理
5. 数据分析
- 销售业绩统计
- 客户转化率分析
- 产品销售趋势
- 实时数据大屏
技术实现要点
1. 后端架构
@SpringBootApplication
public class SalesManagementApplication {
public static void main(String[] args) {
SpringApplication.run(SalesManagementApplication.class, args);
}
}
2. 权限控制
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter());
}
}
3. 数据访问层
@Mapper
public interface SalesOrderMapper extends BaseMapper<SalesOrder> {
@Select("SELECT * FROM sales_order WHERE customer_id = #{customerId}")
List<SalesOrder> findByCustomerId(Long customerId);
}
部署架构
1. 开发环境
- JDK 11+
- Node.js 16+
- MySQL 8.0
- Redis 6.x
2. 服务器配置
- 应用服务器:2核4G
- 数据库服务器:4核8G
- Redis服务器:2核4G
3. 部署流程
- 后端服务打包:
mvn clean package
- 前端构建:
npm run build
- Docker容器化部署
- Nginx反向代理配置
性能优化
1. 数据库优化
- 索引优化
- SQL语句优化
- 分库分表设计
2. 缓存策略
- Redis缓存热点数据
- 本地缓存使用
- 缓存更新策略
3. JVM调优
- 内存分配优化
- GC策略调整
- 线程池配置
安全性考虑
1. 接口安全
- 参数验证
- SQL注入防护
- XSS防护
- CSRF防护
2. 数据安全
- 敏感数据加密
- 数据备份策略
- 访问权限控制
项目亮点
- 采用前后端分离架构,提高开发效率和维护性
- 使用Redis实现分布式Session,支持横向扩展
- 实现基于AOP的操作日志记录
- 集成ELK实现统一日志管理
- 使用Docker实现容器化部署
总结与展望
本项目采用主流的Java全栈技术栈,实现了一个功能完整、性能优异的企业级销售管理系统。通过合理的技术选型和架构设计,系统具有良好的可扩展性和维护性。未来将继续优化系统性能,增加更多智能化功能,如AI辅助决策、智能报表等。
Directory Content Summary
Source Directory: ./enterprise-sales-system
Directory Structure
enterprise-sales-system/
README.md
backend/
pom.xml
src/
main/
java/
com/
example/
sales/
aspect/
LogAspect.java
config/
SecurityConfig.java
controller/
CustomerCategoryController.java
CustomerController.java
CustomerTagController.java
DataAnalysisController.java
InvoiceController.java
ProductCategoryController.java
ProductController.java
ProductInventoryController.java
ProductPriceStrategyController.java
QuotationController.java
SalesContractController.java
SalesOrderController.java
WarehouseController.java
entity/
Customer.java
CustomerCategory.java
CustomerConversion.java
CustomerFollowRecord.java
CustomerTag.java
InventoryTransaction.java
Invoice.java
InvoiceItem.java
Product.java
ProductCategory.java
ProductInventory.java
ProductPriceStrategy.java
ProductSalesTrend.java
Quotation.java
QuotationItem.java
RealtimeMetrics.java
RegionalSales.java
SalesContract.java
SalesFunnel.java
SalesOrder.java
SalesOrderItem.java
SalesPerformance.java
SysPermission.java
SysRole.java
SysUser.java
Warehouse.java
security/
JwtAuthenticationFilter.java
JwtTokenProvider.java
service/
CustomerCategoryService.java
CustomerTagService.java
DataAnalysisService.java
InvoiceService.java
QuotationService.java
SalesContractService.java
SalesOrderService.java
impl/
CustomerCategoryServiceImpl.java
CustomerServiceImpl.java
CustomerTagServiceImpl.java
DataAnalysisServiceImpl.java
InvoiceServiceImpl.java
QuotationServiceImpl.java
SalesContractServiceImpl.java
SalesOrderServiceImpl.java
vo/
ContractQueryVO.java
CustomerAnalysisVO.java
CustomerQueryVO.java
InvoiceQueryVO.java
ProductCategoryQueryVO.java
ProductInventoryQueryVO.java
ProductPriceStrategyQueryVO.java
ProductQueryVO.java
QuotationQueryVO.java
SalesOrderQueryVO.java
resources/
application.yml
target/
classes/
application.yml
com/
example/
sales/
aspect/
config/
entity/
security/
generated-sources/
annotations/
generated-test-sources/
test-annotations/
test-classes/
frontend/
package.json
vite.config.js
src/
api/
analysis/
index.js
customer/
category.js
index.js
tag.js
product/
category.js
index.js
inventory.js
price.js
inventory/
transaction.js
sales/
contract.js
invoice.js
order.js
quotation.js
system/
user.js
warehouse/
index.js
router/
modules/
analysis.js
product.js
views/
analysis/
index.vue
customer/
analysis.vue
index.vue
category/
index.vue
components/
CustomerForm.vue
FollowRecordDialog.vue
tag/
index.vue
dashboard/
index.vue
product/
index.vue
category/
index.vue
components/
InventoryList.vue
PriceStrategyList.vue
ProductForm.vue
sales/
contract/
index.vue
components/
ContractDetail.vue
ContractForm.vue
invoice/
index.vue
components/
InvoiceDetail.vue
InvoiceForm.vue
order/
index.vue
components/
OrderDetail.vue
OrderForm.vue
quotation/
index.vue
components/
QuotationDetail.vue
QuotationForm.vue
system/
user/
index.vue
warehouse/
index.vue
sql/
analysis_tables.sql
customer_tables.sql
init.sql
product_tables.sql
rbac_tables.sql
sales_tables.sql
File Contents
backend\pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>enterprise-sales-system</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
<relativePath/>
</parent>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
<druid.version>1.2.16</druid.version>
<knife4j.version>4.1.0</knife4j.version>
<jjwt.version>0.11.5</jjwt.version>
<hutool.version>5.8.20</hutool.version>
<easyexcel.version>3.3.2</easyexcel.version>
</properties>
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- Knife4j API文档 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
<!-- EasyExcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>${easyexcel.version}</version>
</dependency>
<!-- Hutool工具类 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
backend\src\main\java\com\example\sales\aspect\LogAspect.java
package com.example.sales.aspect;
import com.example.sales.entity.SysOperationLog;
import com.example.sales.service.SysOperationLogService;
import com.example.sales.util.SecurityUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
@Aspect
@Component
@Slf4j
public class LogAspect {
@Autowired
private SysOperationLogService operationLogService;
@Autowired
private ObjectMapper objectMapper;
@Pointcut("@annotation(com.example.sales.annotation.Log)")
public void logPointCut() {}
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
Object result = null;
Exception exception = null;
try {
result = point.proceed();
return result;
} catch (Exception e) {
exception = e;
throw e;
} finally {
saveOperationLog(point, result, exception, System.currentTimeMillis() - beginTime);
}
}
private void saveOperationLog(ProceedingJoinPoint joinPoint, Object result, Exception e, long time) {
try {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
SysOperationLog operationLog = new SysOperationLog();
// 设置操作用户信息
String username = SecurityUtils.getCurrentUsername();
operationLog.setUserId(SecurityUtils.getCurrentUserId());
operationLog.setUsername(username);
// 设置请求信息
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
operationLog.setIp(request.getRemoteAddr());
operationLog.setMethod(request.getMethod() + " " + request.getRequestURI());
}
// 设置请求参数
String params = objectMapper.writeValueAsString(joinPoint.getArgs());
operationLog.setParams(params);
// 设置操作状态
operationLog.setStatus(e == null ? 1 : 0);
if (e != null) {
operationLog.setErrorMsg(e.getMessage());
}
operationLog.setOperationTime(LocalDateTime.now());
// 保存日志
operationLogService.save(operationLog);
} catch (Exception ex) {
log.error("Save operation log failed", ex);
}
}
}
backend\src\main\java\com\example\sales\config\SecurityConfig.java
package com.example.sales.config;
import com.example.sales.security.JwtAuthenticationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf()
.disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/auth/**")
.permitAll()
.antMatchers("/api/test/**")
.permitAll()
.anyRequest()
.authenticated();
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
backend\src\main\java\com\example\sales\controller\CustomerCategoryController.java
package com.example.sales.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.sales.common.Result;
import com.example.sales.entity.CustomerCategory;
import com.example.sales.service.CustomerCategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/customer/category")
public class CustomerCategoryController {
@Autowired
private CustomerCategoryService customerCategoryService;
@GetMapping("/list")
@PreAuthorize("hasAuthority('customer:view')")
public Result<List<CustomerCategory>> list() {
return Result.success(customerCategoryService.list());
}
@GetMapping("/page")
@PreAuthorize("hasAuthority('customer:view')")
public Result<IPage<CustomerCategory>> page(
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String keyword) {
return Result.success(customerCategoryService.getPage(pageNum, pageSize, keyword));
}
@PostMapping
@PreAuthorize("hasAuthority('customer:add')")
public Result<Boolean> add(@RequestBody CustomerCategory category) {
return Result.success(customerCategoryService.save(category));
}
@PutMapping
@PreAuthorize("hasAuthority('customer:edit')")
public Result<Boolean> update(@RequestBody CustomerCategory category) {
return Result.success(customerCategoryService.updateById(category));
}
@DeleteMapping("/{id}")
@PreAuthorize("hasAuthority('customer:delete')")
public Result<Boolean> delete(@PathVariable Long id) {
return Result.success(customerCategoryService.removeById(id));
}
}
backend\src\main\java\com\example\sales\controller\CustomerController.java
package com.example.sales.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.sales.common.Result;
import com.example.sales.entity.Customer;
import com.example.sales.entity.CustomerFollowRecord;
import com.example.sales.service.CustomerService;
import com.example.sales.vo.CustomerAnalysisVO;
import com.example.sales.vo.CustomerQueryVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/customer")
public class CustomerController {
@Autowired
private CustomerService customerService;
@GetMapping("/list")
@PreAuthorize("hasAuthority('customer:view')")
public Result<IPage<Customer>> list(CustomerQueryVO queryVO) {
return Result.success(customerService.getCustomerPage(queryVO));
}
@GetMapping("/{id}")
@PreAuthorize("hasAuthority('customer:view')")
public Result<Customer> getById(@PathVariable Long id) {
return Result.success(customerService.getCustomerById(id));
}
@PostMapping
@PreAuthorize("hasAuthority('customer:add')")
public Result<Boolean> add(@RequestBody Customer customer) {
return Result.success(customerService.saveCustomer(customer));
}
@PutMapping
@PreAuthorize("hasAuthority('customer:edit')")
public Result<Boolean> update(@RequestBody Customer customer) {
return Result.success(customerService.updateCustomer(customer));
}
@DeleteMapping("/{id}")
@PreAuthorize("hasAuthority('customer:delete')")
public Result<Boolean> delete(@PathVariable Long id) {
return Result.success(customerService.removeCustomer(id));
}
@PostMapping("/follow")
@PreAuthorize("hasAuthority('customer:follow')")
public Result<Boolean> addFollowRecord(@RequestBody CustomerFollowRecord record) {
return Result.success(customerService.addFollowRecord(record));
}
@GetMapping("/follow/{customerId}")
@PreAuthorize("hasAuthority('customer:view')")
public Result<List<CustomerFollowRecord>> getFollowRecords(@PathVariable Long customerId) {
return Result.success(customerService.getFollowRecords(customerId));
}
@GetMapping("/analysis/overview")
@PreAuthorize("hasAuthority('customer:view')")
public Result<CustomerAnalysisVO> getAnalysisOverview() {
return Result.success(customerService.getCustomerAnalysis());
}
@GetMapping("/analysis/distribution")
@PreAuthorize("hasAuthority('customer:view')")
public Result<Map<String, Object>> getDistributionData() {
return Result.success(customerService.getCustomerDistribution());
}
@GetMapping("/analysis/trend")
@PreAuthorize("hasAuthority('customer:view')")
public Result<Map<String, Object>> getTrendData() {
return Result.success(customerService.getCustomerTrend());
}
}
backend\src\main\java\com\example\sales\controller\CustomerTagController.java
package com.example.sales.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.sales.common.Result;
import com.example.sales.entity.CustomerTag;
import com.example.sales.service.CustomerTagService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/customer/tag")
public class CustomerTagController {
@Autowired
private CustomerTagService customerTagService;
@GetMapping("/list")
@PreAuthorize("hasAuthority('customer:view')")
public Result<List<CustomerTag>> list() {
return Result.success(customerTagService.list());
}
@GetMapping("/page")
@PreAuthorize("hasAuthority('customer:view')")
public Result<IPage<CustomerTag>> page(
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String keyword) {
return Result.success(customerTagService.getPage(pageNum, pageSize, keyword));
}
@PostMapping
@PreAuthorize("hasAuthority('customer:add')")
public Result<Boolean> add(@RequestBody CustomerTag tag) {
return Result.success(customerTagService.save(tag));
}
@PutMapping
@PreAuthorize("hasAuthority('customer:edit')")
public Result<Boolean> update(@RequestBody CustomerTag tag) {
return Result.success(customerTagService.updateById(tag));
}
@DeleteMapping("/{id}")
@PreAuthorize("hasAuthority('customer:delete')")
public Result<Boolean> delete(@PathVariable Long id) {
return Result.success(customerTagService.removeById(id));
}
}
backend\src\main\java\com\example\sales\controller\DataAnalysisController.java
package com.example.sales.controller;
import com.example.sales.common.api.CommonResult;
import com.example.sales.service.DataAnalysisService;
import com.example.sales.vo.analysis.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/analysis")
public class DataAnalysisController {
@Autowired
private DataAnalysisService dataAnalysisService;
@GetMapping("/performance")
public CommonResult<List<SalesPerformanceVO>> getSalesPerformance(
@RequestParam Integer year,
@RequestParam(required = false) Integer month,
@RequestParam(required = false) Long salesUserId) {
return CommonResult.success(dataAnalysisService.getSalesPerformance(year, month, salesUserId));
}
@GetMapping("/conversion")
public CommonResult<List<CustomerConversionVO>> getCustomerConversion(
@RequestParam Integer year,
@RequestParam(required = false) Integer month) {
return CommonResult.success(dataAnalysisService.getCustomerConversion(year, month));
}
@GetMapping("/product/trend")
public CommonResult<List<ProductSalesTrendVO>> getProductSalesTrend(
@RequestParam Integer year,
@RequestParam(required = false) Integer month,
@RequestParam(required = false) Long productId,
@RequestParam(required = false) Long categoryId) {
return CommonResult.success(dataAnalysisService.getProductSalesTrend(year, month, productId, categoryId));
}
@GetMapping("/realtime/overview")
public CommonResult<Map<String, Object>> getRealtimeOverview() {
return CommonResult.success(dataAnalysisService.getRealtimeOverview());
}
@GetMapping("/realtime/trend")
public CommonResult<Map<String, Object>> getRealtimeTrend(
@RequestParam String metricCode,
@RequestParam Integer hours) {
return CommonResult.success(dataAnalysisService.getRealtimeTrend(metricCode, hours));
}
@GetMapping("/funnel")
public CommonResult<List<SalesFunnelVO>> getSalesFunnel(
@RequestParam Integer year,
@RequestParam(required = false) Integer month) {
return CommonResult.success(dataAnalysisService.getSalesFunnel(year, month));
}
@GetMapping("/regional")
public CommonResult<List<RegionalSalesVO>> getRegionalSales(
@RequestParam Integer year,
@RequestParam(required = false) Integer month) {
return CommonResult.success(dataAnalysisService.getRegionalSales(year, month));
}
@GetMapping("/dashboard")
public CommonResult<Map<String, Object>> getDashboardData() {
return CommonResult.success(dataAnalysisService.getDashboardData());
}
}
backend\src\main\java\com\example\sales\controller\InvoiceController.java
package com.example.sales.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.sales.common.Result;
import com.example.sales.entity.Invoice;
import com.example.sales.service.InvoiceService;
import com.example.sales.vo.InvoiceQueryVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/sales/invoice")
public class InvoiceController {
@Autowired
private InvoiceService invoiceService;
@GetMapping("/page")
@PreAuthorize("hasAuthority('sales:invoice:view')")
public Result<IPage<Invoice>> page(InvoiceQueryVO queryVO) {
return Result.success(invoiceService.getPage(queryVO));
}
@GetMapping("/{id}")
@PreAuthorize("hasAuthority('sales:invoice:view')")
public Result<Invoice> getById(@PathVariable Long id) {
return Result.success(invoiceService.getInvoiceById(id));
}
@PostMapping
@PreAuthorize("hasAuthority('sales:invoice:add')")
public Result<Boolean> add(@RequestBody Invoice invoice) {
return Result.success(invoiceService.saveInvoice(invoice));
}
@PutMapping
@PreAuthorize("hasAuthority('sales:invoice:edit')")
public Result<Boolean> update(@RequestBody Invoice invoice) {
return Result.success(invoiceService.updateInvoice(invoice));
}
@DeleteMapping("/{id}")
@PreAuthorize("hasAuthority('sales:invoice:delete')")
public Result<Boolean> delete(@PathVariable Long id) {
return Result.success(invoiceService.removeById(id));
}
@PostMapping("/{id}/submit")
@PreAuthorize("hasAuthority('sales:invoice:submit')")
public Result<Boolean> submit(@PathVariable Long id) {
return Result.success(invoiceService.submitInvoice(id));
}
@PostMapping("/{id}/issue")
@PreAuthorize("hasAuthority('sales:invoice:issue')")
public Result<Boolean> issue(@PathVariable Long id) {
return Result.success(invoiceService.issueInvoice(id));
}
@PostMapping("/{id}/mail")
@PreAuthorize("hasAuthority('sales:invoice:mail')")
public Result<Boolean> mail(@PathVariable Long id) {
return Result.success(invoiceService.mailInvoice(id));
}
@PostMapping("/{id}/void")
@PreAuthorize("hasAuthority('sales:invoice:void')")
public Result<Boolean> voidInvoice(@PathVariable Long id, @RequestParam String reason) {
return Result.success(invoiceService.voidInvoice(id, reason));
}
}
backend\src\main\java\com\example\sales\controller\ProductCategoryController.java
package com.example.sales.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.sales.common.api.CommonResult;
import com.example.sales.entity.ProductCategory;
import com.example.sales.service.ProductCategoryService;
import com.example.sales.vo.ProductCategoryQueryVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/product/category")
public class ProductCategoryController {
@Autowired
private ProductCategoryService productCategoryService;
@GetMapping("/list")
public CommonResult<List<ProductCategory>> list(ProductCategoryQueryVO queryVO) {
return CommonResult.success(productCategoryService.list(queryVO));
}
@GetMapping("/tree")
public CommonResult<List<ProductCategory>> tree() {
return CommonResult.success(productCategoryService.tree());
}
@GetMapping("/{id}")
public CommonResult<ProductCategory> getById(@PathVariable Long id) {
return CommonResult.success(productCategoryService.getById(id));
}
@PostMapping
public CommonResult<Boolean> add(@RequestBody ProductCategory category) {
return CommonResult.success(productCategoryService.save(category));
}
@PutMapping
public CommonResult<Boolean> update(@RequestBody ProductCategory category) {
return CommonResult.success(productCategoryService.updateById(category));
}
@DeleteMapping("/{id}")
public CommonResult<Boolean> delete(@PathVariable Long id) {
return CommonResult.success(productCategoryService.removeById(id));
}
@PutMapping("/{id}/status")
public CommonResult<Boolean> updateStatus(@PathVariable Long id, @RequestParam String status) {
return CommonResult.success(productCategoryService.updateStatus(id, status));
}
}
backend\src\main\java\com\example\sales\controller\ProductController.java
package com.example.sales.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.sales.common.api.CommonResult;
import com.example.sales.entity.Product;
import com.example.sales.service.ProductService;
import com.example.sales.vo.ProductQueryVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/product")
public class ProductController {
@Autowired
private ProductService productService;
@GetMapping("/page")
public CommonResult<IPage<Product>> page(ProductQueryVO queryVO,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
return CommonResult.success(productService.page(queryVO, pageNum, pageSize));
}
@GetMapping("/{id}")
public CommonResult<Product> getById(@PathVariable Long id) {
return CommonResult.success(productService.getById(id));
}
@PostMapping
public CommonResult<Boolean> add(@RequestBody Product product) {
return CommonResult.success(productService.save(product));
}
@PutMapping
public CommonResult<Boolean> update(@RequestBody Product product) {
return CommonResult.success(productService.updateById(product));
}
@DeleteMapping("/{id}")
public CommonResult<Boolean> delete(@PathVariable Long id) {
return CommonResult.success(productService.removeById(id));
}
@PutMapping("/{id}/status")
public CommonResult<Boolean> updateStatus(@PathVariable Long id, @RequestParam String status) {
return CommonResult.success(productService.updateStatus(id, status));
}
@GetMapping("/code/generate")
public CommonResult<String> generateCode() {
return CommonResult.success(productService.generateCode());
}
}
backend\src\main\java\com\example\sales\controller\ProductInventoryController.java
package com.example.sales.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.sales.common.api.CommonResult;
import com.example.sales.entity.ProductInventory;
import com.example.sales.entity.InventoryTransaction;
import com.example.sales.service.ProductInventoryService;
import com.example.sales.vo.ProductInventoryQueryVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/product/inventory")
public class ProductInventoryController {
@Autowired
private ProductInventoryService inventoryService;
@GetMapping("/page")
public CommonResult<IPage<ProductInventory>> page(ProductInventoryQueryVO queryVO,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
return CommonResult.success(inventoryService.page(queryVO, pageNum, pageSize));
}
@GetMapping("/{id}")
public CommonResult<ProductInventory> getById(@PathVariable Long id) {
return CommonResult.success(inventoryService.getById(id));
}
@PostMapping("/in")
public CommonResult<Boolean> stockIn(@RequestBody InventoryTransaction transaction) {
return CommonResult.success(inventoryService.stockIn(transaction));
}
@PostMapping("/out")
public CommonResult<Boolean> stockOut(@RequestBody InventoryTransaction transaction) {
return CommonResult.success(inventoryService.stockOut(transaction));
}
@PostMapping("/lock")
public CommonResult<Boolean> lockStock(@RequestBody InventoryTransaction transaction) {
return CommonResult.success(inventoryService.lockStock(transaction));
}
@PostMapping("/unlock")
public CommonResult<Boolean> unlockStock(@RequestBody InventoryTransaction transaction) {
return CommonResult.success(inventoryService.unlockStock(transaction));
}
@GetMapping("/alert")
public CommonResult<List<ProductInventory>> listAlertItems() {
return CommonResult.success(inventoryService.listAlertItems());
}
@GetMapping("/transaction/page")
public CommonResult<IPage<InventoryTransaction>> transactionPage(
@RequestParam(required = false) Long productId,
@RequestParam(required = false) String warehouseCode,
@RequestParam(required = false) String transactionType,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
return CommonResult.success(inventoryService.transactionPage(productId, warehouseCode, transactionType, pageNum, pageSize));
}
@PutMapping("/threshold")
public CommonResult<Boolean> updateAlertThreshold(@RequestParam Long id, @RequestParam Integer threshold) {
return CommonResult.success(inventoryService.updateAlertThreshold(id, threshold));
}
}
backend\src\main\java\com\example\sales\controller\ProductPriceStrategyController.java
package com.example.sales.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.sales.common.api.CommonResult;
import com.example.sales.entity.ProductPriceStrategy;
import com.example.sales.service.ProductPriceStrategyService;
import com.example.sales.vo.ProductPriceStrategyQueryVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
@RestController
@RequestMapping("/product/price-strategy")
public class ProductPriceStrategyController {
@Autowired
private ProductPriceStrategyService priceStrategyService;
@GetMapping("/page")
public CommonResult<IPage<ProductPriceStrategy>> page(ProductPriceStrategyQueryVO queryVO,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
return CommonResult.success(priceStrategyService.page(queryVO, pageNum, pageSize));
}
@GetMapping("/{id}")
public CommonResult<ProductPriceStrategy> getById(@PathVariable Long id) {
return CommonResult.success(priceStrategyService.getById(id));
}
@PostMapping
public CommonResult<Boolean> add(@RequestBody ProductPriceStrategy strategy) {
return CommonResult.success(priceStrategyService.save(strategy));
}
@PutMapping
public CommonResult<Boolean> update(@RequestBody ProductPriceStrategy strategy) {
return CommonResult.success(priceStrategyService.updateById(strategy));
}
@DeleteMapping("/{id}")
public CommonResult<Boolean> delete(@PathVariable Long id) {
return CommonResult.success(priceStrategyService.removeById(id));
}
@PutMapping("/{id}/status")
public CommonResult<Boolean> updateStatus(@PathVariable Long id, @RequestParam String status) {
return CommonResult.success(priceStrategyService.updateStatus(id, status));
}
@GetMapping("/calculate")
public CommonResult<BigDecimal> calculatePrice(@RequestParam Long productId,
@RequestParam(required = false) Long customerCategoryId,
@RequestParam Integer quantity) {
return CommonResult.success(priceStrategyService.calculatePrice(productId, customerCategoryId, quantity));
}
}
backend\src\main\java\com\example\sales\controller\QuotationController.java
package com.example.sales.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.sales.common.Result;
import com.example.sales.entity.Quotation;
import com.example.sales.service.QuotationService;
import com.example.sales.vo.QuotationQueryVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/sales/quotation")
public class QuotationController {
@Autowired
private QuotationService quotationService;
@GetMapping("/page")
@PreAuthorize("hasAuthority('sales:quotation:view')")
public Result<IPage<Quotation>> page(QuotationQueryVO queryVO) {
return Result.success(quotationService.getPage(queryVO));
}
@GetMapping("/{id}")
@PreAuthorize("hasAuthority('sales:quotation:view')")
public Result<Quotation> getById(@PathVariable Long id) {
return Result.success(quotationService.getQuotationById(id));
}
@PostMapping
@PreAuthorize("hasAuthority('sales:quotation:add')")
public Result<Boolean> add(@RequestBody Quotation quotation) {
return Result.success(quotationService.saveQuotation(quotation));
}
@PutMapping
@PreAuthorize("hasAuthority('sales:quotation:edit')")
public Result<Boolean> update(@RequestBody Quotation quotation) {
return Result.success(quotationService.updateQuotation(quotation));
}
@DeleteMapping("/{id}")
@PreAuthorize("hasAuthority('sales:quotation:delete')")
public Result<Boolean> delete(@PathVariable Long id) {
return Result.success(quotationService.removeById(id));
}
@PostMapping("/{id}/send")
@PreAuthorize("hasAuthority('sales:quotation:send')")
public Result<Boolean> send(@PathVariable Long id) {
return Result.success(quotationService.sendQuotation(id));
}
@PostMapping("/{id}/accept")
@PreAuthorize("hasAuthority('sales:quotation:process')")
public Result<Boolean> accept(@PathVariable Long id) {
return Result.success(quotationService.acceptQuotation(id));
}
@PostMapping("/{id}/reject")
@PreAuthorize("hasAuthority('sales:quotation:process')")
public Result<Boolean> reject(@PathVariable Long id, @RequestParam String reason) {
return Result.success(quotationService.rejectQuotation(id, reason));
}
}
backend\src\main\java\com\example\sales\controller\SalesContractController.java
package com.example.sales.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.sales.common.Result;
import com.example.sales.entity.SalesContract;
import com.example.sales.service.SalesContractService;
import com.example.sales.vo.ContractQueryVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@RestController
@RequestMapping("/sales/contract")
public class SalesContractController {
@Autowired
private SalesContractService contractService;
@GetMapping("/page")
@PreAuthorize("hasAuthority('sales:contract:view')")
public Result<IPage<SalesContract>> page(ContractQueryVO queryVO) {
return Result.success(contractService.getPage(queryVO));
}
@GetMapping("/{id}")
@PreAuthorize("hasAuthority('sales:contract:view')")
public Result<SalesContract> getById(@PathVariable Long id) {
return Result.success(contractService.getContractById(id));
}
@PostMapping
@PreAuthorize("hasAuthority('sales:contract:add')")
public Result<Boolean> add(@RequestBody SalesContract contract) {
return Result.success(contractService.saveContract(contract));
}
@PutMapping
@PreAuthorize("hasAuthority('sales:contract:edit')")
public Result<Boolean> update(@RequestBody SalesContract contract) {
return Result.success(contractService.updateContract(contract));
}
@DeleteMapping("/{id}")
@PreAuthorize("hasAuthority('sales:contract:delete')")
public Result<Boolean> delete(@PathVariable Long id) {
return Result.success(contractService.removeById(id));
}
@PostMapping("/{id}/sign")
@PreAuthorize("hasAuthority('sales:contract:sign')")
public Result<Boolean> sign(@PathVariable Long id) {
return Result.success(contractService.signContract(id));
}
@PostMapping("/{id}/terminate")
@PreAuthorize("hasAuthority('sales:contract:terminate')")
public Result<Boolean> terminate(@PathVariable Long id, @RequestParam String reason) {
return Result.success(contractService.terminateContract(id, reason));
}
@PostMapping("/upload")
@PreAuthorize("hasAuthority('sales:contract:upload')")
public Result<String> uploadAttachment(@RequestParam("file") MultipartFile file) {
return Result.success(contractService.uploadAttachment(file));
}
}
backend\src\main\java\com\example\sales\controller\SalesOrderController.java
package com.example.sales.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.sales.common.Result;
import com.example.sales.entity.SalesOrder;
import com.example.sales.service.SalesOrderService;
import com.example.sales.vo.SalesOrderQueryVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/sales/order")
public class SalesOrderController {
@Autowired
private SalesOrderService salesOrderService;
@GetMapping("/page")
@PreAuthorize("hasAuthority('sales:order:view')")
public Result<IPage<SalesOrder>> page(SalesOrderQueryVO queryVO) {
return Result.success(salesOrderService.getPage(queryVO));
}
@GetMapping("/{id}")
@PreAuthorize("hasAuthority('sales:order:view')")
public Result<SalesOrder> getById(@PathVariable Long id) {
return Result.success(salesOrderService.getOrderById(id));
}
@PostMapping
@PreAuthorize("hasAuthority('sales:order:add')")
public Result<Boolean> add(@RequestBody SalesOrder order) {
return Result.success(salesOrderService.saveOrder(order));
}
@PutMapping
@PreAuthorize("hasAuthority('sales:order:edit')")
public Result<Boolean> update(@RequestBody SalesOrder order) {
return Result.success(salesOrderService.updateOrder(order));
}
@DeleteMapping("/{id}")
@PreAuthorize("hasAuthority('sales:order:delete')")
public Result<Boolean> delete(@PathVariable Long id) {
return Result.success(salesOrderService.removeById(id));
}
@PostMapping("/{id}/submit")
@PreAuthorize("hasAuthority('sales:order:submit')")
public Result<Boolean> submit(@PathVariable Long id) {
return Result.success(salesOrderService.submitOrder(id));
}
@PostMapping("/{id}/approve")
@PreAuthorize("hasAuthority('sales:order:approve')")
public Result<Boolean> approve(@PathVariable Long id) {
return Result.success(salesOrderService.approveOrder(id));
}
@PostMapping("/{id}/reject")
@PreAuthorize("hasAuthority('sales:order:approve')")
public Result<Boolean> reject(@PathVariable Long id, @RequestParam String reason) {
return Result.success(salesOrderService.rejectOrder(id, reason));
}
}
backend\src\main\java\com\example\sales\controller\WarehouseController.java
package com.example.sales.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.sales.common.api.CommonResult;
import com.example.sales.entity.Warehouse;
import com.example.sales.service.WarehouseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/warehouse")
public class WarehouseController {
@Autowired
private WarehouseService warehouseService;
@GetMapping("/list")
public CommonResult<List<Warehouse>> list(@RequestParam(required = false) String status) {
return CommonResult.success(warehouseService.list(status));
}
@GetMapping("/page")
public CommonResult<IPage<Warehouse>> page(
@RequestParam(required = false) String warehouseCode,
@RequestParam(required = false) String warehouseName,
@RequestParam(required = false) String status,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
return CommonResult.success(warehouseService.page(warehouseCode, warehouseName, status, pageNum, pageSize));
}
@GetMapping("/{id}")
public CommonResult<Warehouse> getById(@PathVariable Long id) {
return CommonResult.success(warehouseService.getById(id));
}
@PostMapping
public CommonResult<Boolean> add(@RequestBody Warehouse warehouse) {
return CommonResult.success(warehouseService.save(warehouse));
}
@PutMapping
public CommonResult<Boolean> update(@RequestBody Warehouse warehouse) {
return CommonResult.success(warehouseService.updateById(warehouse));
}
@DeleteMapping("/{id}")
public CommonResult<Boolean> delete(@PathVariable Long id) {
return CommonResult.success(warehouseService.removeById(id));
}
@PutMapping("/{id}/status")
public CommonResult<Boolean> updateStatus(@PathVariable Long id, @RequestParam String status) {
return CommonResult.success(warehouseService.updateStatus(id, status));
}
@GetMapping("/code/generate")
public CommonResult<String> generateCode() {
return CommonResult.success(warehouseService.generateCode());
}
}
backend\src\main\java\com\example\sales\entity\Customer.java
package com.example.sales.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
@Data
@TableName("cust_info")
public class Customer {
@TableId(type = IdType.AUTO)
private Long id;
private Long categoryId;
private String customerName;
private String contactPerson;
private String contactPhone;
private String email;
private String address;
private String industry;
private String companySize;
private BigDecimal annualRevenue;
private String source;
private String status;
private String creditRating;
private String remarks;
private Long createBy;
@TableField(exist = false)
private String categoryName;
@TableField(exist = false)
private List<CustomerTag> tags;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
backend\src\main\java\com\example\sales\entity\CustomerCategory.java
package com.example.sales.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("cust_category")
public class CustomerCategory {
@TableId(type = IdType.AUTO)
private Long id;
private String categoryName;
private String description;
private Integer sort;
private Boolean enabled;
}
backend\src\main\java\com\example\sales\entity\CustomerConversion.java
package com.example.sales.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@TableName("customer_conversion")
public class CustomerConversion {
@TableId(type = IdType.AUTO)
private Long id;
private Integer year;
private Integer month;
private Integer totalLeads;
private Integer convertedLeads;
private BigDecimal conversionRate;
private Integer avgConversionTime;
private LocalDateTime createdTime;
private LocalDateTime updatedTime;
}
backend\src\main\java\com\example\sales\entity\CustomerFollowRecord.java
package com.example.sales.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("cust_follow_record")
public class CustomerFollowRecord {
@TableId(type = IdType.AUTO)
private Long id;
private Long customerId;
private String content;
private String contactPerson;
private String contactMethod;
private LocalDateTime followTime;
private LocalDateTime nextFollowTime;
private String followResult;
private Long createdBy;
private LocalDateTime createdTime;
}
backend\src\main\java\com\example\sales\entity\CustomerTag.java
package com.example.sales.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("cust_tag")
public class CustomerTag {
@TableId(type = IdType.AUTO)
private Long id;
private String tagName;
private String color;
private Integer sort;
private Boolean enabled;
}
backend\src\main\java\com\example\sales\entity\InventoryTransaction.java
package com.example.sales.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("inventory_transaction")
public class InventoryTransaction {
@TableId(type = IdType.AUTO)
private Long id;
private Long productId;
private String warehouseCode;
private String transactionType;
private Integer quantity;
private String referenceType;
private Long referenceId;
private String remark;
private String createdBy;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdTime;
@TableField(exist = false)
private String productName;
@TableField(exist = false)
private String warehouseName;
}
backend\src\main\java\com\example\sales\entity\Invoice.java
package com.example.sales.entity;
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 lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
@Data
@TableName("invoice")
public class Invoice {
@TableId(type = IdType.AUTO)
private Long id;
private String invoiceNo;
private Long customerId;
private Long orderId;
private String invoiceType;
private BigDecimal invoiceAmount;
private BigDecimal taxRate;
private BigDecimal taxAmount;
private String status;
private String invoiceTitle;
private String taxNo;
private String bankName;
private String bankAccount;
private String contactPhone;
private String mailingAddress;
private String remark;
private Long createdBy;
private LocalDateTime createdTime;
private Long updatedBy;
private LocalDateTime updatedTime;
@TableField(exist = false)
private List<InvoiceItem> items;
@TableField(exist = false)
private String customerName;
@TableField(exist = false)
private String orderNo;
}
backend\src\main\java\com\example\sales\entity\InvoiceItem.java
package com.example.sales.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.math.BigDecimal;
@Data
@TableName("invoice_item")
public class InvoiceItem {
@TableId(type = IdType.AUTO)
private Long id;
private Long invoiceId;
private Long productId;
private String productName;
private String specification;
private String unit;
private Integer quantity;
private BigDecimal unitPrice;
private BigDecimal totalPrice;
private BigDecimal taxRate;
private BigDecimal taxAmount;
}
backend\src\main\java\com\example\sales\entity\Product.java
package com.example.sales.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("product")
public class Product {
@TableId(type = IdType.AUTO)
private Long id;
private String productCode;
private String productName;
private Long categoryId;
private String brand;
private String model;
private String specification;
private String unit;
private String description;
private String status;
private String createdBy;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdTime;
private String updatedBy;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedTime;
@TableField(exist = false)
private String categoryName;
}
backend\src\main\java\com\example\sales\entity\ProductCategory.java
package com.example.sales.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("product_category")
public class ProductCategory {
@TableId(type = IdType.AUTO)
private Long id;
private String categoryName;
private Long parentId;
private Integer level;
private Integer sortOrder;
private String status;
private String createdBy;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdTime;
private String updatedBy;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedTime;
}
backend\src\main\java\com\example\sales\entity\ProductInventory.java
package com.example.sales.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("product_inventory")
public class ProductInventory {
@TableId(type = IdType.AUTO)
private Long id;
private Long productId;
private String warehouseCode;
private Integer quantity;
private Integer lockedQuantity;
private Integer alertThreshold;
private String createdBy;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdTime;
private String updatedBy;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedTime;
@TableField(exist = false)
private String productName;
@TableField(exist = false)
private String warehouseName;
@TableField(exist = false)
private Integer availableQuantity;
public Integer getAvailableQuantity() {
return quantity - lockedQuantity;
}
}
backend\src\main\java\com\example\sales\entity\ProductPriceStrategy.java
package com.example.sales.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
@TableName("product_price_strategy")
public class ProductPriceStrategy {
@TableId(type = IdType.AUTO)
private Long id;
private Long productId;
private String strategyName;
private Long customerCategoryId;
private Integer minQuantity;
private BigDecimal basePrice;
private BigDecimal discountRate;
private LocalDate startDate;
private LocalDate endDate;
private String status;
private String createdBy;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdTime;
private String updatedBy;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedTime;
@TableField(exist = false)
private String productName;
@TableField(exist = false)
private String customerCategoryName;
}
backend\src\main\java\com\example\sales\entity\ProductSalesTrend.java
package com.example.sales.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@TableName("product_sales_trend")
public class ProductSalesTrend {
@TableId(type = IdType.AUTO)
private Long id;
private Long productId;
private Long categoryId;
private Integer year;
private Integer month;
private Integer salesQuantity;
private BigDecimal salesAmount;
private BigDecimal profitAmount;
private LocalDateTime createdTime;
private LocalDateTime updatedTime;
}
backend\src\main\java\com\example\sales\entity\Quotation.java
package com.example.sales.entity;
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 lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
@Data
@TableName("quotation")
public class Quotation {
@TableId(type = IdType.AUTO)
private Long id;
private String quotationNo;
private Long customerId;
private BigDecimal totalAmount;
private Integer validDays;
private String status;
private String remark;
private Long createdBy;
private LocalDateTime createdTime;
private Long updatedBy;
private LocalDateTime updatedTime;
@TableField(exist = false)
private List<QuotationItem> items;
@TableField(exist = false)
private String customerName;
}
backend\src\main\java\com\example\sales\entity\QuotationItem.java
package com.example.sales.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.math.BigDecimal;
@Data
@TableName("quotation_item")
public class QuotationItem {
@TableId(type = IdType.AUTO)
private Long id;
private Long quotationId;
private Long productId;
private String productName;
private Integer quantity;
private BigDecimal unitPrice;
private BigDecimal totalPrice;
private String remark;
}
backend\src\main\java\com\example\sales\entity\RealtimeMetrics.java
package com.example.sales.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@TableName("realtime_metrics")
public class RealtimeMetrics {
@TableId(type = IdType.AUTO)
private Long id;
private String metricCode;
private String metricName;
private BigDecimal metricValue;
private String metricUnit;
private BigDecimal compareValue;
private BigDecimal compareRate;
private LocalDateTime createdTime;
private LocalDateTime updatedTime;
}
backend\src\main\java\com\example\sales\entity\RegionalSales.java
package com.example.sales.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@TableName("regional_sales")
public class RegionalSales {
@TableId(type = IdType.AUTO)
private Long id;
private String province;
private String city;
private Integer year;
private Integer month;
private Integer customerCount;
private BigDecimal contractAmount;
private LocalDateTime createdTime;
private LocalDateTime updatedTime;
}
backend\src\main\java\com\example\sales\entity\SalesContract.java
package com.example.sales.entity;
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 lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
@TableName("sales_contract")
public class SalesContract {
@TableId(type = IdType.AUTO)
private Long id;
private String contractNo;
private Long customerId;
private Long orderId;
private BigDecimal contractAmount;
private LocalDate signDate;
private LocalDate startDate;
private LocalDate endDate;
private String status;
private String content;
private String attachmentUrl;
private String remark;
private Long createdBy;
private LocalDateTime createdTime;
private Long updatedBy;
private LocalDateTime updatedTime;
@TableField(exist = false)
private String customerName;
@TableField(exist = false)
private String orderNo;
}
backend\src\main\java\com\example\sales\entity\SalesFunnel.java
package com.example.sales.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@TableName("sales_funnel")
public class SalesFunnel {
@TableId(type = IdType.AUTO)
private Long id;
private String stage;
private Integer year;
private Integer month;
private Integer customerCount;
private BigDecimal opportunityAmount;
private BigDecimal conversionRate;
private LocalDateTime createdTime;
private LocalDateTime updatedTime;
}
backend\src\main\java\com\example\sales\entity\SalesOrder.java
package com.example.sales.entity;
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 lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
@Data
@TableName("sales_order")
public class SalesOrder {
@TableId(type = IdType.AUTO)
private Long id;
private String orderNo;
private Long customerId;
private BigDecimal totalAmount;
private BigDecimal discountAmount;
private BigDecimal actualAmount;
private String status;
private String paymentStatus;
private String deliveryStatus;
private String remark;
private Long createdBy;
private LocalDateTime createdTime;
private Long updatedBy;
private LocalDateTime updatedTime;
@TableField(exist = false)
private List<SalesOrderItem> items;
@TableField(exist = false)
private String customerName;
}
backend\src\main\java\com\example\sales\entity\SalesOrderItem.java
package com.example.sales.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.math.BigDecimal;
@Data
@TableName("sales_order_item")
public class SalesOrderItem {
@TableId(type = IdType.AUTO)
private Long id;
private Long orderId;
private Long productId;
private String productName;
private Integer quantity;
private BigDecimal unitPrice;
private BigDecimal totalPrice;
private String remark;
}
backend\src\main\java\com\example\sales\entity\SalesPerformance.java
package com.example.sales.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@TableName("sales_performance")
public class SalesPerformance {
@TableId(type = IdType.AUTO)
private Long id;
private Long salesUserId;
private Integer year;
private Integer month;
private Integer contractCount;
private BigDecimal contractAmount;
private BigDecimal receivedAmount;
private BigDecimal commission;
private LocalDateTime createdTime;
private LocalDateTime updatedTime;
}
backend\src\main\java\com\example\sales\entity\SysPermission.java
package com.example.sales.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("sys_permission")
public class SysPermission {
@TableId(type = IdType.AUTO)
private Long id;
private Long parentId;
private String name;
private String permissionCode;
private String path;
private String component;
private Integer type;
private String icon;
private Integer sortOrder;
private Integer status;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
backend\src\main\java\com\example\sales\entity\SysRole.java
package com.example.sales.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("sys_role")
public class SysRole {
@TableId(type = IdType.AUTO)
private Long id;
private String roleName;
private String roleCode;
private String description;
private Integer status;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
backend\src\main\java\com\example\sales\entity\SysUser.java
package com.example.sales.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("sys_user")
public class SysUser {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String password;
private String realName;
private String email;
private String phone;
private Integer status;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
backend\src\main\java\com\example\sales\entity\Warehouse.java
package com.example.sales.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("warehouse")
public class Warehouse {
@TableId(type = IdType.AUTO)
private Long id;
private String warehouseCode;
private String warehouseName;
private String contactPerson;
private String contactPhone;
private String province;
private String city;
private String district;
private String address;
private String status;
private String createdBy;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdTime;
private String updatedBy;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedTime;
}
backend\src\main\java\com\example\sales\security\JwtAuthenticationFilter.java
package com.example.sales.security;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenProvider tokenProvider;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
String jwt = getJwtFromRequest(request);
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
String username = tokenProvider.getUsernameFromToken(jwt);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception ex) {
log.error("Could not set user authentication in security context", ex);
}
filterChain.doFilter(request, response);
}
private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
backend\src\main\java\com\example\sales\security\JwtTokenProvider.java
package com.example.sales.security;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;
@Component
public class JwtTokenProvider {
@Value("${jwt.secret}")
private String jwtSecret;
@Value("${jwt.expiration}")
private long jwtExpirationInMs;
private SecretKey key;
public String generateToken(Authentication authentication) {
UserDetails userPrincipal = (UserDetails) authentication.getPrincipal();
Date now = new Date();
Date expiryDate = new Date(now.getTime() + jwtExpirationInMs);
return Jwts.builder()
.setSubject(userPrincipal.getUsername())
.setIssuedAt(new Date())
.setExpiration(expiryDate)
.signWith(getKey())
.compact();
}
public String getUsernameFromToken(String token) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(getKey())
.build()
.parseClaimsJws(token)
.getBody();
return claims.getSubject();
}
public boolean validateToken(String authToken) {
try {
Jwts.parserBuilder()
.setSigningKey(getKey())
.build()
.parseClaimsJws(authToken);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
private SecretKey getKey() {
if (key == null) {
key = Keys.hmacShaKeyFor(jwtSecret.getBytes(StandardCharsets.UTF_8));
}
return key;
}
}
backend\src\main\java\com\example\sales\service\CustomerCategoryService.java
package com.example.sales.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.sales.entity.CustomerCategory;
public interface CustomerCategoryService extends IService<CustomerCategory> {
IPage<CustomerCategory> getPage(Integer pageNum, Integer pageSize, String keyword);
}
backend\src\main\java\com\example\sales\service\CustomerTagService.java
package com.example.sales.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.sales.entity.CustomerTag;
public interface CustomerTagService extends IService<CustomerTag> {
IPage<CustomerTag> getPage(Integer pageNum, Integer pageSize, String keyword);
}
backend\src\main\java\com\example\sales\service\DataAnalysisService.java
package com.example.sales.service;
import com.example.sales.vo.analysis.*;
import java.util.List;
import java.util.Map;
public interface DataAnalysisService {
/**
* 获取销售业绩数据
*/
List<SalesPerformanceVO> getSalesPerformance(Integer year, Integer month, Long salesUserId);
/**
* 获取客户转化率数据
*/
List<CustomerConversionVO> getCustomerConversion(Integer year, Integer month);
/**
* 获取产品销售趋势数据
*/
List<ProductSalesTrendVO> getProductSalesTrend(Integer year, Integer month, Long productId, Long categoryId);
/**
* 获取实时概览数据
*/
Map<String, Object> getRealtimeOverview();
/**
* 获取实时趋势数据
*/
Map<String, Object> getRealtimeTrend(String metricCode, Integer hours);
/**
* 获取销售漏斗数据
*/
List<SalesFunnelVO> getSalesFunnel(Integer year, Integer month);
/**
* 获取地区销售数据
*/
List<RegionalSalesVO> getRegionalSales(Integer year, Integer month);
/**
* 获取仪表盘数据
*/
Map<String, Object> getDashboardData();
}
backend\src\main\java\com\example\sales\service\InvoiceService.java
package com.example.sales.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.sales.entity.Invoice;
import com.example.sales.vo.InvoiceQueryVO;
public interface InvoiceService extends IService<Invoice> {
IPage<Invoice> getPage(InvoiceQueryVO queryVO);
Invoice getInvoiceById(Long id);
boolean saveInvoice(Invoice invoice);
boolean updateInvoice(Invoice invoice);
boolean submitInvoice(Long id);
boolean issueInvoice(Long id);
boolean mailInvoice(Long id);
boolean voidInvoice(Long id, String reason);
}
backend\src\main\java\com\example\sales\service\QuotationService.java
package com.example.sales.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.sales.entity.Quotation;
import com.example.sales.vo.QuotationQueryVO;
public interface QuotationService extends IService<Quotation> {
IPage<Quotation> getPage(QuotationQueryVO queryVO);
Quotation getQuotationById(Long id);
boolean saveQuotation(Quotation quotation);
boolean updateQuotation(Quotation quotation);
boolean sendQuotation(Long id);
boolean acceptQuotation(Long id);
boolean rejectQuotation(Long id, String reason);
}
backend\src\main\java\com\example\sales\service\SalesContractService.java
package com.example.sales.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.sales.entity.SalesContract;
import com.example.sales.vo.ContractQueryVO;
import org.springframework.web.multipart.MultipartFile;
public interface SalesContractService extends IService<SalesContract> {
IPage<SalesContract> getPage(ContractQueryVO queryVO);
SalesContract getContractById(Long id);
boolean saveContract(SalesContract contract);
boolean updateContract(SalesContract contract);
boolean signContract(Long id);
boolean terminateContract(Long id, String reason);
String uploadAttachment(MultipartFile file);
}
backend\src\main\java\com\example\sales\service\SalesOrderService.java
package com.example.sales.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.sales.entity.SalesOrder;
import com.example.sales.vo.SalesOrderQueryVO;
public interface SalesOrderService extends IService<SalesOrder> {
IPage<SalesOrder> getPage(SalesOrderQueryVO queryVO);
SalesOrder getOrderById(Long id);
boolean saveOrder(SalesOrder order);
boolean updateOrder(SalesOrder order);
boolean submitOrder(Long id);
boolean approveOrder(Long id);
boolean rejectOrder(Long id, String reason);
}
backend\src\main\java\com\example\sales\service\impl\CustomerCategoryServiceImpl.java
package com.example.sales.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.sales.entity.CustomerCategory;
import com.example.sales.mapper.CustomerCategoryMapper;
import com.example.sales.service.CustomerCategoryService;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
@Service
public class CustomerCategoryServiceImpl extends ServiceImpl<CustomerCategoryMapper, CustomerCategory> implements CustomerCategoryService {
@Override
public IPage<CustomerCategory> getPage(Integer pageNum, Integer pageSize, String keyword) {
LambdaQueryWrapper<CustomerCategory> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.hasText(keyword)) {
wrapper.like(CustomerCategory::getCategoryName, keyword);
}
wrapper.orderByAsc(CustomerCategory::getSort);
return this.page(new Page<>(pageNum, pageSize), wrapper);
}
}
backend\src\main\java\com\example\sales\service\impl\CustomerServiceImpl.java
package com.example.sales.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.sales.entity.Customer;
import com.example.sales.entity.CustomerFollowRecord;
import com.example.sales.entity.CustomerTag;
import com.example.sales.mapper.CustomerMapper;
import com.example.sales.service.CustomerService;
import com.example.sales.vo.CustomerAnalysisVO;
import com.example.sales.vo.CustomerQueryVO;
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.*;
import java.util.stream.Collectors;
@Service
public class CustomerServiceImpl extends ServiceImpl<CustomerMapper, Customer> implements CustomerService {
@Autowired
private CustomerTagService customerTagService;
@Autowired
private CustomerFollowRecordService followRecordService;
@Override
public IPage<Customer> getCustomerPage(CustomerQueryVO queryVO) {
LambdaQueryWrapper<Customer> wrapper = new LambdaQueryWrapper<>();
// 构建查询条件
if (StringUtils.hasText(queryVO.getKeyword())) {
wrapper.like(Customer::getCustomerName, queryVO.getKeyword())
.or()
.like(Customer::getContactPerson, queryVO.getKeyword());
}
if (queryVO.getCategoryId() != null) {
wrapper.eq(Customer::getCategoryId, queryVO.getCategoryId());
}
if (StringUtils.hasText(queryVO.getStatus())) {
wrapper.eq(Customer::getStatus, queryVO.getStatus());
}
wrapper.orderByDesc(Customer::getCreateTime);
// 分页查询
Page<Customer> page = new Page<>(queryVO.getPageNum(), queryVO.getPageSize());
IPage<Customer> customerPage = this.page(page, wrapper);
// 填充额外信息
customerPage.getRecords().forEach(this::fillCustomerInfo);
return customerPage;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean saveCustomer(Customer customer) {
// 保存客户基本信息
boolean success = this.save(customer);
// 保存标签关联
if (success && customer.getTags() != null) {
customerTagService.saveBatch(customer.getTags().stream()
.map(tag -> {
tag.setCustomerId(customer.getId());
return tag;
})
.collect(Collectors.toList()));
}
return success;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateCustomer(Customer customer) {
// 更新客户基本信息
boolean success = this.updateById(customer);
if (success && customer.getTags() != null) {
// 删除原有标签关联
customerTagService.remove(new LambdaQueryWrapper<CustomerTag>()
.eq(CustomerTag::getCustomerId, customer.getId()));
// 保存新的标签关联
customerTagService.saveBatch(customer.getTags().stream()
.map(tag -> {
tag.setCustomerId(customer.getId());
return tag;
})
.collect(Collectors.toList()));
}
return success;
}
@Override
public boolean addFollowRecord(CustomerFollowRecord record) {
return followRecordService.save(record);
}
@Override
public List<CustomerFollowRecord> getFollowRecords(Long customerId) {
return followRecordService.list(new LambdaQueryWrapper<CustomerFollowRecord>()
.eq(CustomerFollowRecord::getCustomerId, customerId)
.orderByDesc(CustomerFollowRecord::getCreateTime));
}
@Override
public CustomerAnalysisVO getCustomerAnalysis() {
CustomerAnalysisVO analysisVO = new CustomerAnalysisVO();
// 统计客户总数
analysisVO.setTotalCustomers(this.count());
// 统计各状态客户数量
Map<String, Long> statusCount = this.list().stream()
.collect(Collectors.groupingBy(Customer::getStatus, Collectors.counting()));
analysisVO.setStatusDistribution(statusCount);
// 统计本月新增客户数
analysisVO.setNewCustomersThisMonth(this.count(new LambdaQueryWrapper<Customer>()
.apply("DATE_FORMAT(create_time,'%Y%m') = DATE_FORMAT(NOW(),'%Y%m')")));
return analysisVO;
}
@Override
public Map<String, Object> getCustomerDistribution() {
Map<String, Object> distribution = new HashMap<>();
// 行业分布
distribution.put("industryDistribution", this.list().stream()
.collect(Collectors.groupingBy(Customer::getIndustry, Collectors.counting())));
// 规模分布
distribution.put("sizeDistribution", this.list().stream()
.collect(Collectors.groupingBy(Customer::getCompanySize, Collectors.counting())));
// 来源分布
distribution.put("sourceDistribution", this.list().stream()
.collect(Collectors.groupingBy(Customer::getSource, Collectors.counting())));
return distribution;
}
private void fillCustomerInfo(Customer customer) {
// 填充标签信息
List<CustomerTag> tags = customerTagService.list(new LambdaQueryWrapper<CustomerTag>()
.eq(CustomerTag::getCustomerId, customer.getId()));
customer.setTags(tags);
// 可以填充其他需要的信息
}
}
backend\src\main\java\com\example\sales\service\impl\CustomerTagServiceImpl.java
package com.example.sales.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.sales.entity.CustomerTag;
import com.example.sales.mapper.CustomerTagMapper;
import com.example.sales.service.CustomerTagService;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
@Service
public class CustomerTagServiceImpl extends ServiceImpl<CustomerTagMapper, CustomerTag> implements CustomerTagService {
@Override
public IPage<CustomerTag> getPage(Integer pageNum, Integer pageSize, String keyword) {
LambdaQueryWrapper<CustomerTag> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.hasText(keyword)) {
wrapper.like(CustomerTag::getTagName, keyword);
}
wrapper.orderByAsc(CustomerTag::getSort);
return this.page(new Page<>(pageNum, pageSize), wrapper);
}
}
backend\src\main\java\com\example\sales\service\impl\DataAnalysisServiceImpl.java
package com.example.sales.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.sales.entity.*;
import com.example.sales.mapper.*;
import com.example.sales.service.DataAnalysisService;
import com.example.sales.vo.analysis.*;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
public class DataAnalysisServiceImpl implements DataAnalysisService {
@Autowired
private SalesPerformanceMapper salesPerformanceMapper;
@Autowired
private CustomerConversionMapper customerConversionMapper;
@Autowired
private ProductSalesTrendMapper productSalesTrendMapper;
@Autowired
private RealtimeMetricsMapper realtimeMetricsMapper;
@Autowired
private SalesFunnelMapper salesFunnelMapper;
@Autowired
private RegionalSalesMapper regionalSalesMapper;
@Override
public List<SalesPerformanceVO> getSalesPerformance(Integer year, Integer month, Long salesUserId) {
LambdaQueryWrapper<SalesPerformance> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SalesPerformance::getYear, year);
if (month != null) {
wrapper.eq(SalesPerformance::getMonth, month);
}
if (salesUserId != null) {
wrapper.eq(SalesPerformance::getSalesUserId, salesUserId);
}
wrapper.orderByAsc(SalesPerformance::getMonth);
List<SalesPerformance> list = salesPerformanceMapper.selectList(wrapper);
return list.stream().map(item -> {
SalesPerformanceVO vo = new SalesPerformanceVO();
BeanUtils.copyProperties(item, vo);
return vo;
}).collect(Collectors.toList());
}
@Override
public List<CustomerConversionVO> getCustomerConversion(Integer year, Integer month) {
LambdaQueryWrapper<CustomerConversion> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CustomerConversion::getYear, year);
if (month != null) {
wrapper.eq(CustomerConversion::getMonth, month);
}
wrapper.orderByAsc(CustomerConversion::getMonth);
List<CustomerConversion> list = customerConversionMapper.selectList(wrapper);
return list.stream().map(item -> {
CustomerConversionVO vo = new CustomerConversionVO();
BeanUtils.copyProperties(item, vo);
return vo;
}).collect(Collectors.toList());
}
@Override
public List<ProductSalesTrendVO> getProductSalesTrend(Integer year, Integer month, Long productId, Long categoryId) {
LambdaQueryWrapper<ProductSalesTrend> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(ProductSalesTrend::getYear, year);
if (month != null) {
wrapper.eq(ProductSalesTrend::getMonth, month);
}
if (productId != null) {
wrapper.eq(ProductSalesTrend::getProductId, productId);
}
if (categoryId != null) {
wrapper.eq(ProductSalesTrend::getCategoryId, categoryId);
}
wrapper.orderByAsc(ProductSalesTrend::getMonth);
List<ProductSalesTrend> list = productSalesTrendMapper.selectList(wrapper);
return list.stream().map(item -> {
ProductSalesTrendVO vo = new ProductSalesTrendVO();
BeanUtils.copyProperties(item, vo);
return vo;
}).collect(Collectors.toList());
}
@Override
public Map<String, Object> getRealtimeOverview() {
List<RealtimeMetrics> metrics = realtimeMetricsMapper.selectList(null);
Map<String, Object> result = new HashMap<>();
metrics.forEach(metric -> {
Map<String, Object> metricData = new HashMap<>();
metricData.put("value", metric.getMetricValue());
metricData.put("unit", metric.getMetricUnit());
metricData.put("compareValue", metric.getCompareValue());
metricData.put("compareRate", metric.getCompareRate());
result.put(metric.getMetricCode(), metricData);
});
return result;
}
@Override
public Map<String, Object> getRealtimeTrend(String metricCode, Integer hours) {
// 实现实时趋势数据查询,这里需要结合具体的时序数据库实现
// 可以使用 InfluxDB, Prometheus 等时序数据库
return new HashMap<>();
}
@Override
public List<SalesFunnelVO> getSalesFunnel(Integer year, Integer month) {
LambdaQueryWrapper<SalesFunnel> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SalesFunnel::getYear, year);
if (month != null) {
wrapper.eq(SalesFunnel::getMonth, month);
}
wrapper.orderByAsc(SalesFunnel::getStage);
List<SalesFunnel> list = salesFunnelMapper.selectList(wrapper);
return list.stream().map(item -> {
SalesFunnelVO vo = new SalesFunnelVO();
BeanUtils.copyProperties(item, vo);
return vo;
}).collect(Collectors.toList());
}
@Override
public List<RegionalSalesVO> getRegionalSales(Integer year, Integer month) {
LambdaQueryWrapper<RegionalSales> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(RegionalSales::getYear, year);
if (month != null) {
wrapper.eq(RegionalSales::getMonth, month);
}
wrapper.orderByDesc(RegionalSales::getContractAmount);
List<RegionalSales> list = regionalSalesMapper.selectList(wrapper);
return list.stream().map(item -> {
RegionalSalesVO vo = new RegionalSalesVO();
BeanUtils.copyProperties(item, vo);
return vo;
}).collect(Collectors.toList());
}
@Override
public Map<String, Object> getDashboardData() {
Map<String, Object> result = new HashMap<>();
// 1. 获取实时概览数据
result.put("overview", getRealtimeOverview());
// 2. 获取今年的销售业绩
int currentYear = LocalDateTime.now().getYear();
result.put("performance", getSalesPerformance(currentYear, null, null));
// 3. 获取今年的客户转化率
result.put("conversion", getCustomerConversion(currentYear, null));
// 4. 获取销售漏斗数据
result.put("funnel", getSalesFunnel(currentYear, null));
// 5. 获取地区销售数据
result.put("regional", getRegionalSales(currentYear, null));
return result;
}
}
backend\src\main\java\com\example\sales\service\impl\InvoiceServiceImpl.java
package com.example.sales.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.sales.entity.Invoice;
import com.example.sales.entity.InvoiceItem;
import com.example.sales.mapper.InvoiceMapper;
import com.example.sales.service.InvoiceItemService;
import com.example.sales.service.InvoiceService;
import com.example.sales.vo.InvoiceQueryVO;
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.time.LocalDateTime;
@Service
public class InvoiceServiceImpl extends ServiceImpl<InvoiceMapper, Invoice> implements InvoiceService {
@Autowired
private InvoiceItemService invoiceItemService;
@Override
public IPage<Invoice> getPage(InvoiceQueryVO queryVO) {
LambdaQueryWrapper<Invoice> wrapper = new LambdaQueryWrapper<>();
// 根据发票号查询
if (StringUtils.hasText(queryVO.getInvoiceNo())) {
wrapper.eq(Invoice::getInvoiceNo, queryVO.getInvoiceNo());
}
// 根据客户ID查询
if (queryVO.getCustomerId() != null) {
wrapper.eq(Invoice::getCustomerId, queryVO.getCustomerId());
}
// 根据状态查询
if (StringUtils.hasText(queryVO.getStatus())) {
wrapper.eq(Invoice::getStatus, queryVO.getStatus());
}
// 根据发票类型查询
if (StringUtils.hasText(queryVO.getInvoiceType())) {
wrapper.eq(Invoice::getInvoiceType, queryVO.getInvoiceType());
}
// 根据创建时间范围查询
if (StringUtils.hasText(queryVO.getStartDate()) && StringUtils.hasText(queryVO.getEndDate())) {
wrapper.between(Invoice::getCreatedTime, queryVO.getStartDate(), queryVO.getEndDate());
}
// 按创建时间倒序排序
wrapper.orderByDesc(Invoice::getCreatedTime);
return this.page(new Page<>(queryVO.getPageNum(), queryVO.getPageSize()), wrapper);
}
@Override
public Invoice getInvoiceById(Long id) {
Invoice invoice = this.getById(id);
if (invoice != null) {
// 查询发票明细
LambdaQueryWrapper<InvoiceItem> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(InvoiceItem::getInvoiceId, id);
invoice.setItems(invoiceItemService.list(wrapper));
}
return invoice;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean saveInvoice(Invoice invoice) {
// 设置初始状态
invoice.setStatus("DRAFT");
invoice.setCreatedTime(LocalDateTime.now());
// 生成发票编号
invoice.setInvoiceNo("INV" + System.currentTimeMillis());
// 保存发票
boolean success = this.save(invoice);
if (success && invoice.getItems() != null && !invoice.getItems().isEmpty()) {
// 保存发票明细
invoice.getItems().forEach(item -> item.setInvoiceId(invoice.getId()));
invoiceItemService.saveBatch(invoice.getItems());
}
return success;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateInvoice(Invoice invoice) {
invoice.setUpdatedTime(LocalDateTime.now());
// 更新发票
boolean success = this.updateById(invoice);
if (success && invoice.getItems() != null && !invoice.getItems().isEmpty()) {
// 删除原有明细
LambdaQueryWrapper<InvoiceItem> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(InvoiceItem::getInvoiceId, invoice.getId());
invoiceItemService.remove(wrapper);
// 保存新的明细
invoice.getItems().forEach(item -> item.setInvoiceId(invoice.getId()));
invoiceItemService.saveBatch(invoice.getItems());
}
return success;
}
@Override
public boolean submitInvoice(Long id) {
Invoice invoice = this.getById(id);
if (invoice != null && "DRAFT".equals(invoice.getStatus())) {
invoice.setStatus("SUBMITTED");
invoice.setUpdatedTime(LocalDateTime.now());
return this.updateById(invoice);
}
return false;
}
@Override
public boolean issueInvoice(Long id) {
Invoice invoice = this.getById(id);
if (invoice != null && "SUBMITTED".equals(invoice.getStatus())) {
invoice.setStatus("ISSUED");
invoice.setUpdatedTime(LocalDateTime.now());
return this.updateById(invoice);
}
return false;
}
@Override
public boolean mailInvoice(Long id) {
Invoice invoice = this.getById(id);
if (invoice != null && "ISSUED".equals(invoice.getStatus())) {
invoice.setStatus("MAILED");
invoice.setUpdatedTime(LocalDateTime.now());
return this.updateById(invoice);
}
return false;
}
@Override
public boolean voidInvoice(Long id, String reason) {
Invoice invoice = this.getById(id);
if (invoice != null && !"VOID".equals(invoice.getStatus())) {
invoice.setStatus("VOID");
invoice.setRemark(reason);
invoice.setUpdatedTime(LocalDateTime.now());
return this.updateById(invoice);
}
return false;
}
}
backend\src\main\java\com\example\sales\service\impl\QuotationServiceImpl.java
package com.example.sales.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.sales.entity.Quotation;
import com.example.sales.entity.QuotationItem;
import com.example.sales.mapper.QuotationMapper;
import com.example.sales.service.QuotationItemService;
import com.example.sales.service.QuotationService;
import com.example.sales.vo.QuotationQueryVO;
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.time.LocalDateTime;
@Service
public class QuotationServiceImpl extends ServiceImpl<QuotationMapper, Quotation> implements QuotationService {
@Autowired
private QuotationItemService quotationItemService;
@Override
public IPage<Quotation> getPage(QuotationQueryVO queryVO) {
LambdaQueryWrapper<Quotation> wrapper = new LambdaQueryWrapper<>();
// 根据报价单号查询
if (StringUtils.hasText(queryVO.getQuotationNo())) {
wrapper.eq(Quotation::getQuotationNo, queryVO.getQuotationNo());
}
// 根据客户ID查询
if (queryVO.getCustomerId() != null) {
wrapper.eq(Quotation::getCustomerId, queryVO.getCustomerId());
}
// 根据状态查询
if (StringUtils.hasText(queryVO.getStatus())) {
wrapper.eq(Quotation::getStatus, queryVO.getStatus());
}
// 根据创建时间范围查询
if (StringUtils.hasText(queryVO.getStartDate()) && StringUtils.hasText(queryVO.getEndDate())) {
wrapper.between(Quotation::getCreatedTime, queryVO.getStartDate(), queryVO.getEndDate());
}
// 按创建时间倒序排序
wrapper.orderByDesc(Quotation::getCreatedTime);
return this.page(new Page<>(queryVO.getPageNum(), queryVO.getPageSize()), wrapper);
}
@Override
public Quotation getQuotationById(Long id) {
Quotation quotation = this.getById(id);
if (quotation != null) {
// 查询报价单明细
LambdaQueryWrapper<QuotationItem> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(QuotationItem::getQuotationId, id);
quotation.setItems(quotationItemService.list(wrapper));
}
return quotation;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean saveQuotation(Quotation quotation) {
// 设置初始状态
quotation.setStatus("DRAFT");
quotation.setCreatedTime(LocalDateTime.now());
// 保存报价单
boolean success = this.save(quotation);
if (success && quotation.getItems() != null && !quotation.getItems().isEmpty()) {
// 保存报价单明细
quotation.getItems().forEach(item -> item.setQuotationId(quotation.getId()));
quotationItemService.saveBatch(quotation.getItems());
}
return success;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateQuotation(Quotation quotation) {
quotation.setUpdatedTime(LocalDateTime.now());
// 更新报价单
boolean success = this.updateById(quotation);
if (success && quotation.getItems() != null && !quotation.getItems().isEmpty()) {
// 删除原有明细
LambdaQueryWrapper<QuotationItem> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(QuotationItem::getQuotationId, quotation.getId());
quotationItemService.remove(wrapper);
// 保存新的明细
quotation.getItems().forEach(item -> item.setQuotationId(quotation.getId()));
quotationItemService.saveBatch(quotation.getItems());
}
return success;
}
@Override
public boolean sendQuotation(Long id) {
Quotation quotation = this.getById(id);
if (quotation != null && "DRAFT".equals(quotation.getStatus())) {
quotation.setStatus("SENT");
quotation.setUpdatedTime(LocalDateTime.now());
return this.updateById(quotation);
}
return false;
}
@Override
public boolean acceptQuotation(Long id) {
Quotation quotation = this.getById(id);
if (quotation != null && "SENT".equals(quotation.getStatus())) {
quotation.setStatus("ACCEPTED");
quotation.setUpdatedTime(LocalDateTime.now());
return this.updateById(quotation);
}
return false;
}
@Override
public boolean rejectQuotation(Long id, String reason) {
Quotation quotation = this.getById(id);
if (quotation != null && "SENT".equals(quotation.getStatus())) {
quotation.setStatus("REJECTED");
quotation.setRemark(reason);
quotation.setUpdatedTime(LocalDateTime.now());
return this.updateById(quotation);
}
return false;
}
}
backend\src\main\java\com\example\sales\service\impl\SalesContractServiceImpl.java
package com.example.sales.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.sales.entity.SalesContract;
import com.example.sales.mapper.SalesContractMapper;
import com.example.sales.service.SalesContractService;
import com.example.sales.vo.ContractQueryVO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDateTime;
import java.util.UUID;
@Service
public class SalesContractServiceImpl extends ServiceImpl<SalesContractMapper, SalesContract> implements SalesContractService {
@Override
public IPage<SalesContract> getPage(ContractQueryVO queryVO) {
LambdaQueryWrapper<SalesContract> wrapper = new LambdaQueryWrapper<>();
// 根据合同编号查询
if (StringUtils.hasText(queryVO.getContractNo())) {
wrapper.eq(SalesContract::getContractNo, queryVO.getContractNo());
}
// 根据客户ID查询
if (queryVO.getCustomerId() != null) {
wrapper.eq(SalesContract::getCustomerId, queryVO.getCustomerId());
}
// 根据状态查询
if (StringUtils.hasText(queryVO.getStatus())) {
wrapper.eq(SalesContract::getStatus, queryVO.getStatus());
}
// 根据签订时间范围查询
if (StringUtils.hasText(queryVO.getStartDate()) && StringUtils.hasText(queryVO.getEndDate())) {
wrapper.between(SalesContract::getSignDate, queryVO.getStartDate(), queryVO.getEndDate());
}
// 按创建时间倒序排序
wrapper.orderByDesc(SalesContract::getCreatedTime);
return this.page(new Page<>(queryVO.getPageNum(), queryVO.getPageSize()), wrapper);
}
@Override
public SalesContract getContractById(Long id) {
return this.getById(id);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean saveContract(SalesContract contract) {
// 设置初始状态
contract.setStatus("DRAFT");
contract.setCreatedTime(LocalDateTime.now());
// 生成合同编号
contract.setContractNo("CT" + System.currentTimeMillis());
return this.save(contract);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateContract(SalesContract contract) {
contract.setUpdatedTime(LocalDateTime.now());
return this.updateById(contract);
}
@Override
public boolean signContract(Long id) {
SalesContract contract = this.getById(id);
if (contract != null && "DRAFT".equals(contract.getStatus())) {
contract.setStatus("SIGNED");
contract.setUpdatedTime(LocalDateTime.now());
return this.updateById(contract);
}
return false;
}
@Override
public boolean terminateContract(Long id, String reason) {
SalesContract contract = this.getById(id);
if (contract != null && "EXECUTING".equals(contract.getStatus())) {
contract.setStatus("TERMINATED");
contract.setRemark(reason);
contract.setUpdatedTime(LocalDateTime.now());
return this.updateById(contract);
}
return false;
}
@Override
public String uploadAttachment(MultipartFile file) {
// TODO: 实现文件上传逻辑
String fileName = UUID.randomUUID().toString() + "_" + file.getOriginalFilename();
// 这里需要实现具体的文件上传逻辑,可以是上传到本地文件系统或者云存储
return "/uploads/" + fileName;
}
}
backend\src\main\java\com\example\sales\service\impl\SalesOrderServiceImpl.java
package com.example.sales.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.sales.entity.SalesOrder;
import com.example.sales.entity.SalesOrderItem;
import com.example.sales.mapper.SalesOrderMapper;
import com.example.sales.service.SalesOrderItemService;
import com.example.sales.service.SalesOrderService;
import com.example.sales.vo.SalesOrderQueryVO;
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.time.LocalDateTime;
@Service
public class SalesOrderServiceImpl extends ServiceImpl<SalesOrderMapper, SalesOrder> implements SalesOrderService {
@Autowired
private SalesOrderItemService orderItemService;
@Override
public IPage<SalesOrder> getPage(SalesOrderQueryVO queryVO) {
LambdaQueryWrapper<SalesOrder> wrapper = new LambdaQueryWrapper<>();
// 根据订单号查询
if (StringUtils.hasText(queryVO.getOrderNo())) {
wrapper.eq(SalesOrder::getOrderNo, queryVO.getOrderNo());
}
// 根据客户ID查询
if (queryVO.getCustomerId() != null) {
wrapper.eq(SalesOrder::getCustomerId, queryVO.getCustomerId());
}
// 根据状态查询
if (StringUtils.hasText(queryVO.getStatus())) {
wrapper.eq(SalesOrder::getStatus, queryVO.getStatus());
}
// 根据付款状态查询
if (StringUtils.hasText(queryVO.getPaymentStatus())) {
wrapper.eq(SalesOrder::getPaymentStatus, queryVO.getPaymentStatus());
}
// 根据发货状态查询
if (StringUtils.hasText(queryVO.getDeliveryStatus())) {
wrapper.eq(SalesOrder::getDeliveryStatus, queryVO.getDeliveryStatus());
}
// 根据创建时间范围查询
if (StringUtils.hasText(queryVO.getStartDate()) && StringUtils.hasText(queryVO.getEndDate())) {
wrapper.between(SalesOrder::getCreatedTime, queryVO.getStartDate(), queryVO.getEndDate());
}
// 按创建时间倒序排序
wrapper.orderByDesc(SalesOrder::getCreatedTime);
return this.page(new Page<>(queryVO.getPageNum(), queryVO.getPageSize()), wrapper);
}
@Override
public SalesOrder getOrderById(Long id) {
SalesOrder order = this.getById(id);
if (order != null) {
// 查询订单明细
LambdaQueryWrapper<SalesOrderItem> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SalesOrderItem::getOrderId, id);
order.setItems(orderItemService.list(wrapper));
}
return order;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean saveOrder(SalesOrder order) {
// 设置初始状态
order.setStatus("DRAFT");
order.setPaymentStatus("UNPAID");
order.setDeliveryStatus("UNDELIVERED");
order.setCreatedTime(LocalDateTime.now());
// 保存订单
boolean success = this.save(order);
if (success && order.getItems() != null && !order.getItems().isEmpty()) {
// 保存订单明细
order.getItems().forEach(item -> item.setOrderId(order.getId()));
orderItemService.saveBatch(order.getItems());
}
return success;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateOrder(SalesOrder order) {
order.setUpdatedTime(LocalDateTime.now());
// 更新订单
boolean success = this.updateById(order);
if (success && order.getItems() != null && !order.getItems().isEmpty()) {
// 删除原有明细
LambdaQueryWrapper<SalesOrderItem> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SalesOrderItem::getOrderId, order.getId());
orderItemService.remove(wrapper);
// 保存新的明细
order.getItems().forEach(item -> item.setOrderId(order.getId()));
orderItemService.saveBatch(order.getItems());
}
return success;
}
@Override
public boolean submitOrder(Long id) {
SalesOrder order = this.getById(id);
if (order != null && "DRAFT".equals(order.getStatus())) {
order.setStatus("SUBMITTED");
order.setUpdatedTime(LocalDateTime.now());
return this.updateById(order);
}
return false;
}
@Override
public boolean approveOrder(Long id) {
SalesOrder order = this.getById(id);
if (order != null && "SUBMITTED".equals(order.getStatus())) {
order.setStatus("APPROVED");
order.setUpdatedTime(LocalDateTime.now());
return this.updateById(order);
}
return false;
}
@Override
public boolean rejectOrder(Long id, String reason) {
SalesOrder order = this.getById(id);
if (order != null && "SUBMITTED".equals(order.getStatus())) {
order.setStatus("REJECTED");
order.setRemark(reason);
order.setUpdatedTime(LocalDateTime.now());
return this.updateById(order);
}
return false;
}
}
backend\src\main\java\com\example\sales\vo\ContractQueryVO.java
package com.example.sales.vo;
import lombok.Data;
@Data
public class ContractQueryVO {
private String keyword;
private String contractNo;
private Long customerId;
private String status;
private String startDate;
private String endDate;
private Integer pageNum = 1;
private Integer pageSize = 10;
}
backend\src\main\java\com\example\sales\vo\CustomerAnalysisVO.java
package com.example.sales.vo;
import lombok.Data;
import java.util.Map;
@Data
public class CustomerAnalysisVO {
private Long totalCustomers;
private Long newCustomersThisMonth;
private Map<String, Long> statusDistribution;
private Double conversionRate;
}
backend\src\main\java\com\example\sales\vo\CustomerQueryVO.java
package com.example.sales.vo;
import lombok.Data;
@Data
public class CustomerQueryVO {
private String keyword;
private Long categoryId;
private String status;
private Long tagId;
private Integer pageNum = 1;
private Integer pageSize = 10;
}
backend\src\main\java\com\example\sales\vo\InvoiceQueryVO.java
package com.example.sales.vo;
import lombok.Data;
@Data
public class InvoiceQueryVO {
private String keyword;
private String invoiceNo;
private Long customerId;
private String status;
private String invoiceType;
private String startDate;
private String endDate;
private Integer pageNum = 1;
private Integer pageSize = 10;
}
backend\src\main\java\com\example\sales\vo\ProductCategoryQueryVO.java
package com.example.sales.vo;
import lombok.Data;
@Data
public class ProductCategoryQueryVO {
private String categoryName;
private Long parentId;
private String status;
}
backend\src\main\java\com\example\sales\vo\ProductInventoryQueryVO.java
package com.example.sales.vo;
import lombok.Data;
@Data
public class ProductInventoryQueryVO {
private Long productId;
private String warehouseCode;
private Boolean belowThreshold;
}
backend\src\main\java\com\example\sales\vo\ProductPriceStrategyQueryVO.java
package com.example.sales.vo;
import lombok.Data;
@Data
public class ProductPriceStrategyQueryVO {
private Long productId;
private String strategyName;
private Long customerCategoryId;
private String status;
}
backend\src\main\java\com\example\sales\vo\ProductQueryVO.java
package com.example.sales.vo;
import lombok.Data;
@Data
public class ProductQueryVO {
private String productCode;
private String productName;
private Long categoryId;
private String brand;
private String status;
}
backend\src\main\java\com\example\sales\vo\QuotationQueryVO.java
package com.example.sales.vo;
import lombok.Data;
@Data
public class QuotationQueryVO {
private String keyword;
private String quotationNo;
private Long customerId;
private String status;
private String startDate;
private String endDate;
private Integer pageNum = 1;
private Integer pageSize = 10;
}
backend\src\main\java\com\example\sales\vo\SalesOrderQueryVO.java
package com.example.sales.vo;
import lombok.Data;
@Data
public class SalesOrderQueryVO {
private String keyword;
private String orderNo;
private Long customerId;
private String status;
private String paymentStatus;
private String deliveryStatus;
private String startDate;
private String endDate;
private Integer pageNum = 1;
private Integer pageSize = 10;
}
backend\src\main\resources\application.yml
server:
port: 8080
servlet:
context-path: /api
spring:
application:
name: enterprise-sales-system
# 数据源配置
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/sales_system?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: 123456
druid:
# 初始连接数
initial-size: 5
# 最小连接池数量
min-idle: 10
# 最大连接池数量
max-active: 20
# 配置获取连接等待超时的时间
max-wait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
time-between-eviction-runs-millis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
min-evictable-idle-time-millis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
max-evictable-idle-time-millis: 900000
# 配置检测连接是否有效
validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: false
test-on-return: false
# Redis配置
redis:
host: localhost
port: 6379
password:
database: 0
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0
# MyBatis Plus配置
mybatis-plus:
mapper-locations: classpath*:mapper/**/*Mapper.xml
type-aliases-package: com.example.sales.entity
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
# 主键类型
id-type: auto
# 逻辑删除字段
logic-delete-field: deleted
# 逻辑删除值
logic-delete-value: 1
# 逻辑未删除值
logic-not-delete-value: 0
# JWT配置
jwt:
# JWT加解密使用的密钥
secret: abcdefghijklmnopqrstuvwxyz
# JWT的超期限时间(60*60*24)
expiration: 86400
# JWT负载中拿到开头
tokenHead: Bearer
# 文件上传配置
file:
# 文件上传路径
upload-path: D:/upload/
# 文件访问路径
access-path: /upload/**
# 文件访问URL前缀
access-url: http://localhost:8080/api/upload/
# Knife4j配置
knife4j:
enable: true
setting:
language: zh-CN
enable-swagger-models: true
enable-document-manage: true
swagger-model-name: 实体类列表
basic:
enable: false
backend\target\classes\application.yml
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/sales_management?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: root
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.sales.entity
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
jwt:
secret: your-secret-key
expiration: 86400000 # 24小时
logging:
level:
com.example.sales: debug
frontend\package.json
{
"name": "enterprise-sales-system",
"version": "1.0.0",
"description": "企业销售管理系统",
"author": "example",
"license": "MIT",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore",
"format": "prettier --write src/"
},
"dependencies": {
"@element-plus/icons-vue": "^2.1.0",
"axios": "^1.4.0",
"echarts": "^5.4.3",
"element-plus": "^2.3.9",
"js-cookie": "^3.0.5",
"nprogress": "^0.2.0",
"pinia": "^2.1.6",
"vue": "^3.3.4",
"vue-router": "^4.2.4",
"vue-echarts": "^6.6.1"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.3.2",
"@vitejs/plugin-vue": "^4.2.3",
"@vue/eslint-config-prettier": "^8.0.0",
"eslint": "^8.46.0",
"eslint-plugin-vue": "^9.16.1",
"prettier": "^3.0.1",
"sass": "^1.64.2",
"unplugin-auto-import": "^0.16.6",
"unplugin-vue-components": "^0.25.1",
"vite": "^4.4.9"
}
}
frontend\vite.config.js
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
}
}
}
})
frontend\src\api\analysis\index.js
import request from '@/utils/request'
export function getSalesPerformance(params) {
return request({
url: '/analysis/performance',
method: 'get',
params
})
}
export function getCustomerConversion(params) {
return request({
url: '/analysis/conversion',
method: 'get',
params
})
}
export function getProductSalesTrend(params) {
return request({
url: '/analysis/product/trend',
method: 'get',
params
})
}
export function getRealtimeOverview() {
return request({
url: '/analysis/realtime/overview',
method: 'get'
})
}
export function getRealtimeTrend(params) {
return request({
url: '/analysis/realtime/trend',
method: 'get',
params
})
}
export function getSalesFunnel(params) {
return request({
url: '/analysis/funnel',
method: 'get',
params
})
}
export function getRegionalSales(params) {
return request({
url: '/analysis/regional',
method: 'get',
params
})
}
export function getDashboardData() {
return request({
url: '/analysis/dashboard',
method: 'get'
})
}
frontend\src\api\customer\category.js
import request from '@/utils/request'
export function getCategoryList() {
return request({
url: '/customer/category/list',
method: 'get'
})
}
export function getCategoryPage(params) {
return request({
url: '/customer/category/page',
method: 'get',
params
})
}
export function addCategory(data) {
return request({
url: '/customer/category',
method: 'post',
data
})
}
export function updateCategory(data) {
return request({
url: '/customer/category',
method: 'put',
data
})
}
export function deleteCategory(id) {
return request({
url: `/customer/category/${id}`,
method: 'delete'
})
}
frontend\src\api\customer\index.js
import request from '@/utils/request'
// 获取客户列表
export function getCustomerList(params) {
return request({
url: '/api/customer/list',
method: 'get',
params
})
}
// 获取客户详情
export function getCustomerDetail(id) {
return request({
url: `/api/customer/${id}`,
method: 'get'
})
}
// 新增客户
export function addCustomer(data) {
return request({
url: '/api/customer',
method: 'post',
data
})
}
// 更新客户
export function updateCustomer(data) {
return request({
url: '/api/customer',
method: 'put',
data
})
}
// 删除客户
export function deleteCustomer(id) {
return request({
url: `/api/customer/${id}`,
method: 'delete'
})
}
// 添加跟进记录
export function addFollowRecord(data) {
return request({
url: '/api/customer/follow',
method: 'post',
data
})
}
// 获取跟进记录列表
export function getFollowRecords(customerId) {
return request({
url: `/api/customer/follow/${customerId}`,
method: 'get'
})
}
// 获取客户分析概览
export function getAnalysisOverview() {
return request({
url: '/api/customer/analysis/overview',
method: 'get'
})
}
// 获取客户分布数据
export function getDistributionData() {
return request({
url: '/api/customer/analysis/distribution',
method: 'get'
})
}
// 获取客户趋势数据
export function getTrendData() {
return request({
url: '/api/customer/analysis/trend',
method: 'get'
})
}
frontend\src\api\customer\tag.js
import request from '@/utils/request'
export function getTagList() {
return request({
url: '/customer/tag/list',
method: 'get'
})
}
export function getTagPage(params) {
return request({
url: '/customer/tag/page',
method: 'get',
params
})
}
export function addTag(data) {
return request({
url: '/customer/tag',
method: 'post',
data
})
}
export function updateTag(data) {
return request({
url: '/customer/tag',
method: 'put',
data
})
}
export function deleteTag(id) {
return request({
url: `/customer/tag/${id}`,
method: 'delete'
})
}
frontend\src\api\product\category.js
import request from '@/utils/request'
// 获取分类列表
export function listCategory(params) {
return request({
url: '/product/category/list',
method: 'get',
params
})
}
// 获取分类树
export function listCategoryTree() {
return request({
url: '/product/category/tree',
method: 'get'
})
}
// 获取分类详情
export function getCategory(id) {
return request({
url: `/product/category/${id}`,
method: 'get'
})
}
// 新增分类
export function addCategory(data) {
return request({
url: '/product/category',
method: 'post',
data
})
}
// 更新分类
export function updateCategory(data) {
return request({
url: '/product/category',
method: 'put',
data
})
}
// 删除分类
export function deleteCategory(id) {
return request({
url: `/product/category/${id}`,
method: 'delete'
})
}
// 更新分类状态
export function updateCategoryStatus(id, status) {
return request({
url: `/product/category/${id}/status`,
method: 'put',
params: { status }
})
}
frontend\src\api\product\index.js
import request from '@/utils/request'
// 获取产品分页列表
export function getProductPage(params) {
return request({
url: '/product/page',
method: 'get',
params
})
}
// 获取产品详情
export function getProduct(id) {
return request({
url: `/product/${id}`,
method: 'get'
})
}
// 新增产品
export function addProduct(data) {
return request({
url: '/product',
method: 'post',
data
})
}
// 更新产品
export function updateProduct(data) {
return request({
url: '/product',
method: 'put',
data
})
}
// 删除产品
export function deleteProduct(id) {
return request({
url: `/product/${id}`,
method: 'delete'
})
}
// 更新产品状态
export function updateProductStatus(id, status) {
return request({
url: `/product/${id}/status`,
method: 'put',
params: { status }
})
}
// 生成产品编码
export function generateProductCode() {
return request({
url: '/product/code/generate',
method: 'get'
})
}
// 导出产品
export function exportProduct(params) {
return request({
url: '/product/export',
method: 'get',
params,
responseType: 'blob'
})
}