修复子任务积分计算错误的bug
问题:当子任务包含多个任务类型时,updateTaskItemsQuantityAndScore方法 只用第一个任务类型的单价计算总积分,导致积分显示错误。 解决: 1. 先更新typeDetails JSON,再从更新后的typeDetails中重新计算总积分 2. 新增calculateTotalScoreFromTypeDetails方法从JSON中累加所有类型积分 3. 新增calculateTotalQuantityFromTypeDetails方法计算总数量 4. 同时更新平均单价以保持数据一致性
This commit is contained in:
parent
2d08524f41
commit
1d66f0d7f5
104
CLAUDE.md
Normal file
104
CLAUDE.md
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
DesignHub is a full-stack Designer Task Management Platform (设计师任务抢单管理系统) - a task distribution and management system with a point-based incentive mechanism.
|
||||||
|
|
||||||
|
**Tech Stack:**
|
||||||
|
- **Frontend**: Vue 3 + TypeScript + Ant Design Vue + Pinia + Vite
|
||||||
|
- **Backend**: Spring Boot 3.x (Java 17) + MySQL + MyBatis Plus + Redis
|
||||||
|
- **Authentication**: JWT tokens with RBAC (7 roles)
|
||||||
|
|
||||||
|
## Common Commands
|
||||||
|
|
||||||
|
### Frontend (cd frontend)
|
||||||
|
```bash
|
||||||
|
pnpm install # Install dependencies
|
||||||
|
pnpm dev # Start dev server (port 3000)
|
||||||
|
pnpm build # Build for production
|
||||||
|
pnpm lint # Lint and fix code
|
||||||
|
pnpm format # Format with Prettier
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backend (cd backend)
|
||||||
|
```bash
|
||||||
|
mvn clean install -DskipTests # Build project
|
||||||
|
mvn spring-boot:run # Start dev server (port 8080)
|
||||||
|
java -jar target/designhub-backend-*.jar # Run compiled JAR
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database
|
||||||
|
```bash
|
||||||
|
mysql -u root -p < database/schema.sql
|
||||||
|
mysql -u root -p < database/init-data.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Backend Structure
|
||||||
|
```
|
||||||
|
backend/src/main/java/com/designhub/
|
||||||
|
├── annotation/ # Custom annotations (@RequirePermission, @RequireRole)
|
||||||
|
├── common/ # Result wrapper, ResultCode enums
|
||||||
|
├── config/ # Spring configs (Cors, MyBatis, Swagger, Web)
|
||||||
|
├── controller/ # REST APIs (38 controllers)
|
||||||
|
├── dto/ # Request DTOs
|
||||||
|
├── entity/ # JPA entities (53 tables)
|
||||||
|
├── exception/ # Business exceptions
|
||||||
|
├── interceptor/ # Auth interceptor
|
||||||
|
├── mapper/ # MyBatis mappers
|
||||||
|
├── service/ # Business logic (41 services)
|
||||||
|
├── util/ # JwtUtil, ExcelExportUtil, etc.
|
||||||
|
└── vo/ # Response View Objects
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend Structure
|
||||||
|
```
|
||||||
|
frontend/src/
|
||||||
|
├── api/ # Axios API modules (task.ts, user.ts, auth.ts, etc.)
|
||||||
|
├── components/ # Reusable components (TaskCard, Navbar, etc.)
|
||||||
|
├── composables/ # Vue composables
|
||||||
|
├── layouts/ # Layout wrappers
|
||||||
|
├── router/ # Vue Router config
|
||||||
|
├── stores/ # Pinia stores (auth, config, siteSettings)
|
||||||
|
├── types/ # TypeScript interfaces
|
||||||
|
├── utils/ # Utility functions
|
||||||
|
└── views/ # Page components (27 views)
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Documentation
|
||||||
|
- Swagger UI: http://localhost:8080/doc.html (when backend running)
|
||||||
|
- Frontend dev server proxies `/api` to `http://localhost:8080`
|
||||||
|
|
||||||
|
## Key Conventions
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
- Custom permission annotations: `@RequirePermission`, `@RequireRole` on controller methods
|
||||||
|
- Response format: `Result<T>` wrapper with `ResultCode` enum for status codes
|
||||||
|
- JWT tokens stored in `Authorization` header (Bearer scheme)
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
- Auto-imported Vue components via `unplugin-vue-components`
|
||||||
|
- API calls use axios with JWT in localStorage
|
||||||
|
- Vue Composition API (`<script setup>`)
|
||||||
|
|
||||||
|
### Git Commit Format
|
||||||
|
```
|
||||||
|
feat: 新功能
|
||||||
|
fix: Bug修复
|
||||||
|
docs: 文档更新
|
||||||
|
style: 代码格式
|
||||||
|
refactor: 重构
|
||||||
|
perf: 性能优化
|
||||||
|
test: 测试相关
|
||||||
|
```
|
||||||
|
|
||||||
|
## Entry Points
|
||||||
|
- Frontend main: `frontend/src/main.ts`
|
||||||
|
- Backend main: `backend/src/main/java/com/designhub/DesignHubApplication.java`
|
||||||
|
- Dev config: `backend/src/main/resources/application-dev.yml`
|
||||||
|
|
||||||
|
## Default Credentials
|
||||||
|
- Admin: `admin` / `123456`
|
||||||
@ -1094,36 +1094,8 @@ public class TaskApprovalService {
|
|||||||
for (TaskItem item : taskItems) {
|
for (TaskItem item : taskItems) {
|
||||||
boolean needUpdate = false;
|
boolean needUpdate = false;
|
||||||
|
|
||||||
// 检查子任务的 taskTypeId 是否在更新列表中
|
// 🔥 先更新 typeDetails JSON 字段(如果存在)
|
||||||
if (item.getTaskTypeId() != null && typeUpdateMap.containsKey(item.getTaskTypeId())) {
|
// 这样后面可以从更新后的 typeDetails 中正确计算总积分
|
||||||
TaskUpdateDTO.TaskTypeUpdateDTO typeUpdate = typeUpdateMap.get(item.getTaskTypeId());
|
|
||||||
Integer newQuantity = typeUpdate.getQuantity();
|
|
||||||
|
|
||||||
if (newQuantity != null && !newQuantity.equals(item.getQuantity())) {
|
|
||||||
// 获取单价积分
|
|
||||||
java.math.BigDecimal unitScore = item.getUnitScore();
|
|
||||||
if (unitScore == null) {
|
|
||||||
// 如果没有单价,从配置中获取
|
|
||||||
DemandType demandType = configService.getDemandTypeById(item.getTaskTypeId());
|
|
||||||
if (demandType != null && demandType.getUnitScore() != null) {
|
|
||||||
unitScore = demandType.getUnitScore();
|
|
||||||
} else {
|
|
||||||
unitScore = java.math.BigDecimal.ZERO;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新数量和总积分
|
|
||||||
item.setQuantity(newQuantity);
|
|
||||||
item.setTotalScore(unitScore.multiply(new java.math.BigDecimal(newQuantity)));
|
|
||||||
item.setUpdateTime(LocalDateTime.now());
|
|
||||||
needUpdate = true;
|
|
||||||
|
|
||||||
log.info("更新子任务数量和积分: itemId={}, taskTypeId={}, oldQuantity={}, newQuantity={}, unitScore={}, newTotalScore={}",
|
|
||||||
item.getId(), item.getTaskTypeId(), item.getQuantity(), newQuantity, unitScore, item.getTotalScore());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 🔥 同时更新 typeDetails JSON 字段(如果存在)
|
|
||||||
if (item.getTypeDetails() != null && !item.getTypeDetails().isEmpty()) {
|
if (item.getTypeDetails() != null && !item.getTypeDetails().isEmpty()) {
|
||||||
String updatedTypeDetails = updateTypeDetailsJson(item.getTypeDetails(), typeUpdateMap);
|
String updatedTypeDetails = updateTypeDetailsJson(item.getTypeDetails(), typeUpdateMap);
|
||||||
if (!item.getTypeDetails().equals(updatedTypeDetails)) {
|
if (!item.getTypeDetails().equals(updatedTypeDetails)) {
|
||||||
@ -1131,6 +1103,51 @@ public class TaskApprovalService {
|
|||||||
item.setUpdateTime(LocalDateTime.now());
|
item.setUpdateTime(LocalDateTime.now());
|
||||||
needUpdate = true;
|
needUpdate = true;
|
||||||
log.info("更新子任务typeDetails: itemId={}, newTypeDetails={}", item.getId(), updatedTypeDetails);
|
log.info("更新子任务typeDetails: itemId={}, newTypeDetails={}", item.getId(), updatedTypeDetails);
|
||||||
|
|
||||||
|
// 🔥 从更新后的 typeDetails 中重新计算总积分和总数量
|
||||||
|
// 这是修复多任务类型子任务积分计算错误的关键
|
||||||
|
java.math.BigDecimal newTotalScore = calculateTotalScoreFromTypeDetails(updatedTypeDetails);
|
||||||
|
int newTotalQuantity = calculateTotalQuantityFromTypeDetails(updatedTypeDetails);
|
||||||
|
|
||||||
|
if (newTotalScore != null) {
|
||||||
|
item.setTotalScore(newTotalScore);
|
||||||
|
log.info("从typeDetails重新计算子任务总积分: itemId={}, newTotalScore={}", item.getId(), newTotalScore);
|
||||||
|
}
|
||||||
|
if (newTotalQuantity > 0) {
|
||||||
|
item.setQuantity(newTotalQuantity);
|
||||||
|
// 重新计算平均单价
|
||||||
|
if (newTotalScore != null && newTotalQuantity > 0) {
|
||||||
|
item.setUnitScore(newTotalScore.divide(new java.math.BigDecimal(newTotalQuantity), 2, java.math.RoundingMode.HALF_UP));
|
||||||
|
}
|
||||||
|
log.info("从typeDetails重新计算子任务总数量: itemId={}, newTotalQuantity={}", item.getId(), newTotalQuantity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果没有 typeDetails,使用原来的单类型逻辑
|
||||||
|
if (item.getTaskTypeId() != null && typeUpdateMap.containsKey(item.getTaskTypeId())) {
|
||||||
|
TaskUpdateDTO.TaskTypeUpdateDTO typeUpdate = typeUpdateMap.get(item.getTaskTypeId());
|
||||||
|
Integer newQuantity = typeUpdate.getQuantity();
|
||||||
|
|
||||||
|
if (newQuantity != null && !newQuantity.equals(item.getQuantity())) {
|
||||||
|
// 获取单价积分
|
||||||
|
java.math.BigDecimal unitScore = item.getUnitScore();
|
||||||
|
if (unitScore == null) {
|
||||||
|
DemandType demandType = configService.getDemandTypeById(item.getTaskTypeId());
|
||||||
|
if (demandType != null && demandType.getUnitScore() != null) {
|
||||||
|
unitScore = demandType.getUnitScore();
|
||||||
|
} else {
|
||||||
|
unitScore = java.math.BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item.setQuantity(newQuantity);
|
||||||
|
item.setTotalScore(unitScore.multiply(new java.math.BigDecimal(newQuantity)));
|
||||||
|
item.setUpdateTime(LocalDateTime.now());
|
||||||
|
needUpdate = true;
|
||||||
|
|
||||||
|
log.info("更新单类型子任务数量和积分: itemId={}, taskTypeId={}, newQuantity={}, newTotalScore={}",
|
||||||
|
item.getId(), item.getTaskTypeId(), newQuantity, item.getTotalScore());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1142,6 +1159,52 @@ public class TaskApprovalService {
|
|||||||
log.info("完成子任务数量和积分更新: taskId={}, 子任务数量={}", taskId, taskItems.size());
|
log.info("完成子任务数量和积分更新: taskId={}, 子任务数量={}", taskId, taskItems.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 typeDetails JSON 中计算总积分
|
||||||
|
*/
|
||||||
|
private java.math.BigDecimal calculateTotalScoreFromTypeDetails(String typeDetails) {
|
||||||
|
try {
|
||||||
|
com.fasterxml.jackson.databind.JsonNode rootNode = objectMapper.readTree(typeDetails);
|
||||||
|
if (!rootNode.isArray()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
java.math.BigDecimal totalScore = java.math.BigDecimal.ZERO;
|
||||||
|
for (com.fasterxml.jackson.databind.JsonNode itemNode : rootNode) {
|
||||||
|
if (itemNode.has("totalScore")) {
|
||||||
|
totalScore = totalScore.add(new java.math.BigDecimal(itemNode.get("totalScore").asDouble()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return totalScore.setScale(2, java.math.RoundingMode.HALF_UP);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("计算typeDetails总积分失败: {}", e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 typeDetails JSON 中计算总数量
|
||||||
|
*/
|
||||||
|
private int calculateTotalQuantityFromTypeDetails(String typeDetails) {
|
||||||
|
try {
|
||||||
|
com.fasterxml.jackson.databind.JsonNode rootNode = objectMapper.readTree(typeDetails);
|
||||||
|
if (!rootNode.isArray()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int totalQuantity = 0;
|
||||||
|
for (com.fasterxml.jackson.databind.JsonNode itemNode : rootNode) {
|
||||||
|
if (itemNode.has("quantity")) {
|
||||||
|
totalQuantity += itemNode.get("quantity").asInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return totalQuantity;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("计算typeDetails总数量失败: {}", e.getMessage());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新 typeDetails JSON 中的数量
|
* 更新 typeDetails JSON 中的数量
|
||||||
*/
|
*/
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user