diff --git a/backend/src/main/java/com/bycrm/common/Constants.java b/backend/src/main/java/com/bycrm/common/Constants.java index 06741b2..01ba43c 100644 --- a/backend/src/main/java/com/bycrm/common/Constants.java +++ b/backend/src/main/java/com/bycrm/common/Constants.java @@ -55,6 +55,11 @@ public class Constants { */ public static final int REPORT_STATUS_EXPIRED = 3; + /** + * 报备状态 - 已作废 + */ + public static final int REPORT_STATUS_CANCELED = 4; + /** * 用户角色 - 管理员 */ diff --git a/backend/src/main/java/com/bycrm/controller/ReportController.java b/backend/src/main/java/com/bycrm/controller/ReportController.java index 9d9a55f..d61bf8d 100644 --- a/backend/src/main/java/com/bycrm/controller/ReportController.java +++ b/backend/src/main/java/com/bycrm/controller/ReportController.java @@ -5,6 +5,7 @@ import com.bycrm.common.Result; import com.bycrm.dto.PageQuery; import com.bycrm.dto.ReportAuditDTO; import com.bycrm.dto.ReportDTO; +import com.bycrm.dto.ReportUpdateDTO; import com.bycrm.service.ReportService; import com.bycrm.vo.ReportVO; import io.swagger.annotations.Api; @@ -72,6 +73,20 @@ public class ReportController { return Result.success(); } + /** + * 更新报备 + */ + @ApiOperation("更新报备") + @PutMapping("/{id}") + public Result updateReport( + @ApiParam("报备ID") @PathVariable Long id, + @RequestBody ReportUpdateDTO updateDTO, + HttpServletRequest request) { + Long currentUserId = (Long) request.getAttribute("currentUserId"); + reportService.updateReport(id, updateDTO, currentUserId); + return Result.success(); + } + /** * 审核报备 */ diff --git a/backend/src/main/java/com/bycrm/dto/ReportDTO.java b/backend/src/main/java/com/bycrm/dto/ReportDTO.java index 776337e..135468a 100644 --- a/backend/src/main/java/com/bycrm/dto/ReportDTO.java +++ b/backend/src/main/java/com/bycrm/dto/ReportDTO.java @@ -2,7 +2,7 @@ package com.bycrm.dto; import lombok.Data; -import javax.validation.constraints.NotNull; +import javax.validation.constraints.NotBlank; import java.io.Serializable; /** @@ -14,10 +14,27 @@ public class ReportDTO implements Serializable { private static final long serialVersionUID = 1L; /** - * 客户ID + * 学校ID(可选,从数据源中选择时提供) */ - @NotNull(message = "客户ID不能为空") - private Long customerId; + private Long schoolId; + + /** + * 学校名称(必填) + */ + @NotBlank(message = "学校名称不能为空") + private String schoolName; + + /** + * 所属产品 + */ + @NotBlank(message = "所属产品不能为空") + private String product; + + /** + * 项目类型 + */ + @NotBlank(message = "项目类型不能为空") + private String projectType; /** * 报备说明 diff --git a/backend/src/main/java/com/bycrm/dto/ReportUpdateDTO.java b/backend/src/main/java/com/bycrm/dto/ReportUpdateDTO.java new file mode 100644 index 0000000..4e09a99 --- /dev/null +++ b/backend/src/main/java/com/bycrm/dto/ReportUpdateDTO.java @@ -0,0 +1,44 @@ +package com.bycrm.dto; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.io.Serializable; + +/** + * 报备更新 DTO + */ +@Data +public class ReportUpdateDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 所属产品 + */ + @NotBlank(message = "所属产品不能为空") + private String product; + + /** + * 项目类型 + */ + @NotBlank(message = "项目类型不能为空") + private String projectType; + + /** + * 报备说明 + */ + private String description; + + /** + * 状态 + */ + @NotNull(message = "状态不能为空") + private Integer status; + + /** + * 作废原因(当状态为作废时必填) + */ + private String cancelReason; +} diff --git a/backend/src/main/java/com/bycrm/entity/Report.java b/backend/src/main/java/com/bycrm/entity/Report.java index 89b4f51..89a1c28 100644 --- a/backend/src/main/java/com/bycrm/entity/Report.java +++ b/backend/src/main/java/com/bycrm/entity/Report.java @@ -26,9 +26,24 @@ public class Report implements Serializable { private Long dealerId; /** - * 客户ID + * 学校ID(从 crm_school 选择) */ - private Long customerId; + private Long schoolId; + + /** + * 学校名称(冗余存储,方便查询) + */ + private String schoolName; + + /** + * 所属产品 + */ + private String product; + + /** + * 项目类型 + */ + private String projectType; /** * 报备说明 @@ -36,7 +51,7 @@ public class Report implements Serializable { private String description; /** - * 状态:0-待审核 1-已通过 2-已驳回 3-已失效 + * 状态:0-待审核 1-已通过 2-已驳回 3-已失效 4-已作废 */ private Integer status; @@ -45,6 +60,11 @@ public class Report implements Serializable { */ private String rejectReason; + /** + * 作废原因 + */ + private String cancelReason; + /** * 保护期开始日期 */ @@ -73,14 +93,4 @@ public class Report implements Serializable { * 关联查询字段 - 经销商名称 */ private String dealerName; - - /** - * 关联查询字段 - 客户名称 - */ - private String customerName; - - /** - * 关联查询字段 - 客户电话 - */ - private String customerPhone; } diff --git a/backend/src/main/java/com/bycrm/mapper/ReportMapper.java b/backend/src/main/java/com/bycrm/mapper/ReportMapper.java index 7e0e637..d8e7c7d 100644 --- a/backend/src/main/java/com/bycrm/mapper/ReportMapper.java +++ b/backend/src/main/java/com/bycrm/mapper/ReportMapper.java @@ -19,9 +19,18 @@ public interface ReportMapper { Report selectById(@Param("id") Long id); /** - * 查询客户的有效报备 + * 查询学校+产品+项目类型的有效报备(根据学校ID) */ - Report selectValidByCustomerId(@Param("customerId") Long customerId); + Report selectValidBySchoolAndProduct(@Param("schoolId") Long schoolId, + @Param("product") String product, + @Param("projectType") String projectType); + + /** + * 查询学校名称+产品+项目类型的有效报备(根据学校名称) + */ + Report selectValidBySchoolNameAndProduct(@Param("schoolName") String schoolName, + @Param("product") String product, + @Param("projectType") String projectType); /** * 分页查询报备 diff --git a/backend/src/main/java/com/bycrm/mapper/SchoolMapper.java b/backend/src/main/java/com/bycrm/mapper/SchoolMapper.java index 47ecb43..0836b43 100644 --- a/backend/src/main/java/com/bycrm/mapper/SchoolMapper.java +++ b/backend/src/main/java/com/bycrm/mapper/SchoolMapper.java @@ -12,6 +12,11 @@ import java.util.List; @Mapper public interface SchoolMapper { + /** + * 根据ID查询学校 + */ + School selectById(@Param("id") Long id); + /** * 插入学校 */ diff --git a/backend/src/main/java/com/bycrm/service/ReportService.java b/backend/src/main/java/com/bycrm/service/ReportService.java index 152121f..6aab5e1 100644 --- a/backend/src/main/java/com/bycrm/service/ReportService.java +++ b/backend/src/main/java/com/bycrm/service/ReportService.java @@ -4,6 +4,7 @@ import com.bycrm.common.PageResult; import com.bycrm.dto.PageQuery; import com.bycrm.dto.ReportAuditDTO; import com.bycrm.dto.ReportDTO; +import com.bycrm.dto.ReportUpdateDTO; import com.bycrm.vo.ReportVO; /** @@ -27,6 +28,11 @@ public interface ReportService { */ void createReport(ReportDTO reportDTO, Long currentUserId); + /** + * 更新报备 + */ + void updateReport(Long id, ReportUpdateDTO updateDTO, Long currentUserId); + /** * 审核报备 */ diff --git a/backend/src/main/java/com/bycrm/service/impl/ReportServiceImpl.java b/backend/src/main/java/com/bycrm/service/impl/ReportServiceImpl.java index b181941..5e070b3 100644 --- a/backend/src/main/java/com/bycrm/service/impl/ReportServiceImpl.java +++ b/backend/src/main/java/com/bycrm/service/impl/ReportServiceImpl.java @@ -5,12 +5,13 @@ import com.bycrm.common.PageResult; import com.bycrm.dto.PageQuery; import com.bycrm.dto.ReportAuditDTO; import com.bycrm.dto.ReportDTO; +import com.bycrm.dto.ReportUpdateDTO; import com.bycrm.entity.Report; -import com.bycrm.entity.Customer; +import com.bycrm.entity.School; import com.bycrm.entity.User; import com.bycrm.exception.BusinessException; -import com.bycrm.mapper.CustomerMapper; import com.bycrm.mapper.ReportMapper; +import com.bycrm.mapper.SchoolMapper; import com.bycrm.mapper.UserMapper; import com.bycrm.service.ReportService; import com.bycrm.service.SystemConfigService; @@ -32,16 +33,16 @@ import java.util.stream.Collectors; public class ReportServiceImpl implements ReportService { private final ReportMapper reportMapper; - private final CustomerMapper customerMapper; + private final SchoolMapper schoolMapper; private final UserMapper userMapper; private final SystemConfigService systemConfigService; public ReportServiceImpl(ReportMapper reportMapper, - CustomerMapper customerMapper, + SchoolMapper schoolMapper, UserMapper userMapper, SystemConfigService systemConfigService) { this.reportMapper = reportMapper; - this.customerMapper = customerMapper; + this.schoolMapper = schoolMapper; this.userMapper = userMapper; this.systemConfigService = systemConfigService; } @@ -86,23 +87,47 @@ public class ReportServiceImpl implements ReportService { throw new BusinessException("您未关联经销商,无法提交报备"); } - // 检查客户是否存在 - Customer customer = customerMapper.selectById(reportDTO.getCustomerId()); - if (customer == null) { - throw new BusinessException("客户不存在"); + // 防撞单校验:根据是否有 schoolId 选择不同的校验方式 + Report existingReport = null; + if (reportDTO.getSchoolId() != null) { + // 有学校ID,按学校ID+产品+项目类型校验 + existingReport = reportMapper.selectValidBySchoolAndProduct( + reportDTO.getSchoolId(), + reportDTO.getProduct(), + reportDTO.getProjectType() + ); + } else { + // 没有学校ID,按学校名称+产品+项目类型校验 + existingReport = reportMapper.selectValidBySchoolNameAndProduct( + reportDTO.getSchoolName(), + reportDTO.getProduct(), + reportDTO.getProjectType() + ); } - // 防撞单校验:检查该客户是否已存在有效报备 - Report existingReport = reportMapper.selectValidByCustomerId(reportDTO.getCustomerId()); Boolean allowOverlap = systemConfigService.getBooleanConfigValue("report.allow.overlap", false); if (existingReport != null && !allowOverlap) { - throw new BusinessException("该客户已被其他经销商报备,无法重复报备"); + throw new BusinessException("该项目已报备"); + } + + // 如果有学校ID,查询学校信息 + String schoolName = reportDTO.getSchoolName(); + Long schoolId = reportDTO.getSchoolId(); + + if (schoolId != null) { + School school = schoolMapper.selectById(schoolId); + if (school != null) { + schoolName = school.getSchoolName(); + } } // 创建报备 Report report = new Report(); report.setDealerId(currentUser.getDealerId()); - report.setCustomerId(reportDTO.getCustomerId()); + report.setSchoolId(schoolId); // 可以为 NULL + report.setSchoolName(schoolName); + report.setProduct(reportDTO.getProduct()); + report.setProjectType(reportDTO.getProjectType()); report.setDescription(reportDTO.getDescription()); report.setStatus(Constants.REPORT_STATUS_PENDING); report.setCreatedAt(LocalDateTime.now()); @@ -138,11 +163,6 @@ public class ReportServiceImpl implements ReportService { report.setProtectStartDate(today); Integer protectDays = systemConfigService.getIntegerConfigValue("report.protect.days", Constants.DEFAULT_PROTECT_DAYS); report.setProtectEndDate(today.plusDays(protectDays)); - - // 更新客户状态为保护中 - Customer customer = customerMapper.selectById(report.getCustomerId()); - customer.setStatus(Constants.CUSTOMER_STATUS_PROTECTED); - customerMapper.update(customer); } else { // 审核驳回 report.setStatus(Constants.REPORT_STATUS_REJECTED); @@ -153,6 +173,43 @@ public class ReportServiceImpl implements ReportService { reportMapper.update(report); } + @Override + @Transactional(rollbackFor = Exception.class) + public void updateReport(Long id, ReportUpdateDTO updateDTO, Long currentUserId) { + // 获取当前用户 + User currentUser = userMapper.selectById(currentUserId); + if (currentUser.getRole() != Constants.USER_ROLE_ADMIN) { + throw new BusinessException("只有管理员才能编辑报备"); + } + + Report report = reportMapper.selectById(id); + if (report == null) { + throw new BusinessException("报备不存在"); + } + + // 只有已通过的报备才能编辑 + if (report.getStatus() != Constants.REPORT_STATUS_APPROVED) { + throw new BusinessException("只能编辑已通过的报备"); + } + + // 如果状态改为作废,必须填写作废原因 + if (updateDTO.getStatus() == Constants.REPORT_STATUS_CANCELED) { + if (updateDTO.getCancelReason() == null || updateDTO.getCancelReason().trim().isEmpty()) { + throw new BusinessException("作废时必须填写作废原因"); + } + } + + // 更新报备信息 + report.setProduct(updateDTO.getProduct()); + report.setProjectType(updateDTO.getProjectType()); + report.setDescription(updateDTO.getDescription()); + report.setStatus(updateDTO.getStatus()); + report.setCancelReason(updateDTO.getCancelReason()); + report.setUpdatedAt(LocalDateTime.now()); + + reportMapper.update(report); + } + @Override @Transactional(rollbackFor = Exception.class) public void withdrawReport(Long id, Long currentUserId) { @@ -189,15 +246,6 @@ public class ReportServiceImpl implements ReportService { // 批量更新报备状态 List ids = expiringReports.stream().map(Report::getId).collect(Collectors.toList()); reportMapper.batchUpdateExpired(ids); - - // 批量更新客户状态为可报备 - expiringReports.forEach(report -> { - Customer customer = customerMapper.selectById(report.getCustomerId()); - if (customer != null && customer.getStatus() == Constants.CUSTOMER_STATUS_PROTECTED) { - customer.setStatus(Constants.CUSTOMER_STATUS_AVAILABLE); - customerMapper.update(customer); - } - }); } @Override @@ -236,6 +284,9 @@ public class ReportServiceImpl implements ReportService { case Constants.REPORT_STATUS_EXPIRED: vo.setStatusDesc("已失效"); break; + case Constants.REPORT_STATUS_CANCELED: + vo.setStatusDesc("已作废"); + break; default: vo.setStatusDesc("未知"); } diff --git a/backend/src/main/java/com/bycrm/vo/ReportVO.java b/backend/src/main/java/com/bycrm/vo/ReportVO.java index d56b851..245bf9e 100644 --- a/backend/src/main/java/com/bycrm/vo/ReportVO.java +++ b/backend/src/main/java/com/bycrm/vo/ReportVO.java @@ -31,19 +31,24 @@ public class ReportVO implements Serializable { private String dealerName; /** - * 客户ID + * 学校ID */ - private Long customerId; + private Long schoolId; /** - * 客户名称 + * 学校名称 */ - private String customerName; + private String schoolName; /** - * 客户电话 + * 所属产品 */ - private String customerPhone; + private String product; + + /** + * 项目类型 + */ + private String projectType; /** * 报备说明 @@ -51,7 +56,7 @@ public class ReportVO implements Serializable { private String description; /** - * 状态:0-待审核 1-已通过 2-已驳回 3-已失效 + * 状态:0-待审核 1-已通过 2-已驳回 3-已失效 4-已作废 */ private Integer status; @@ -65,6 +70,11 @@ public class ReportVO implements Serializable { */ private String rejectReason; + /** + * 作废原因 + */ + private String cancelReason; + /** * 保护期开始日期 */ diff --git a/backend/src/main/resources/mapper/ReportMapper.xml b/backend/src/main/resources/mapper/ReportMapper.xml index ca15377..bf79882 100644 --- a/backend/src/main/resources/mapper/ReportMapper.xml +++ b/backend/src/main/resources/mapper/ReportMapper.xml @@ -6,45 +6,52 @@ - + + + + + - - - SELECT * FROM crm_report - WHERE customer_id = #{customerId} + WHERE school_id = #{schoolId} + AND product = #{product} + AND project_type = #{projectType} + AND status IN (0, 1) + LIMIT 1 + + + - INSERT INTO crm_report (dealer_id, customer_id, description, status, protect_start_date, protect_end_date) - VALUES (#{dealerId}, #{customerId}, #{description}, #{status}, #{protectStartDate}, #{protectEndDate}) + INSERT INTO crm_report (dealer_id, school_id, school_name, product, project_type, description, status, protect_start_date, protect_end_date) + VALUES (#{dealerId}, #{schoolId}, #{schoolName}, #{product}, #{projectType}, #{description}, #{status}, #{protectStartDate}, #{protectEndDate}) UPDATE crm_report + product = #{product}, + project_type = #{projectType}, + description = #{description}, status = #{status}, reject_reason = #{rejectReason}, + cancel_reason = #{cancelReason}, protect_start_date = #{protectStartDate}, protect_end_date = #{protectEndDate}, + updated_at = NOW() WHERE id = #{id} diff --git a/backend/src/main/resources/mapper/SchoolMapper.xml b/backend/src/main/resources/mapper/SchoolMapper.xml index 05826e2..7e28439 100644 --- a/backend/src/main/resources/mapper/SchoolMapper.xml +++ b/backend/src/main/resources/mapper/SchoolMapper.xml @@ -12,6 +12,12 @@ + + INSERT INTO crm_school (school_code, school_name, location, created_at, updated_at) VALUES (#{schoolCode}, #{schoolName}, #{location}, #{createdAt}, #{updatedAt}) diff --git a/frontend/src/api/report.ts b/frontend/src/api/report.ts index e371b4e..9a22f45 100644 --- a/frontend/src/api/report.ts +++ b/frontend/src/api/report.ts @@ -1,5 +1,5 @@ import { http } from '@/utils/request' -import type { Report, ReportForm, ReportAuditForm, PageQuery, PageResult } from '@/types' +import type { Report, ReportForm, ReportAuditForm, ReportUpdateForm, PageQuery, PageResult } from '@/types' // 分页查询报备 export const getReportPage = (params: PageQuery & { @@ -21,6 +21,11 @@ export const createReport = (data: ReportForm) => { return http.post('/report', data) } +// 更新报备 +export const updateReport = (id: number, data: ReportUpdateForm) => { + return http.put(`/report/${id}`, data) +} + // 审核报备 export const auditReport = (id: number, data: ReportAuditForm) => { return http.put(`/report/${id}/audit`, data) diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 5a6feff..d083809 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -60,13 +60,15 @@ export interface Report { id: number dealerId: number dealerName: string - customerId: number - customerName: string - customerPhone?: string + schoolId?: number + schoolName?: string + product?: string + projectType?: string description?: string status: number statusDesc: string rejectReason?: string + cancelReason?: string protectStartDate?: string protectEndDate?: string remainDays?: number @@ -75,7 +77,10 @@ export interface Report { } export interface ReportForm { - customerId: number | null + schoolId?: number + schoolName: string + product: string + projectType: string description?: string } @@ -84,6 +89,14 @@ export interface ReportAuditForm { rejectReason?: string } +export interface ReportUpdateForm { + product: string + projectType: string + description?: string + status: number + cancelReason?: string +} + // 分页相关类型 export interface PageQuery { current: number @@ -104,22 +117,6 @@ export interface DictItem { value: string | number } -// 报备进展记录相关类型 -export interface ReportProgress { - id: number - reportId: number - progressContent: string - createdBy: number - creatorName?: string - createdAt: string - updatedAt: string -} - -export interface ReportProgressForm { - reportId: number - progressContent: string -} - // 系统配置相关类型 export interface SystemConfig { id: number diff --git a/frontend/src/views/Dashboard.vue b/frontend/src/views/Dashboard.vue index 437c0d9..7bf1f6b 100644 --- a/frontend/src/views/Dashboard.vue +++ b/frontend/src/views/Dashboard.vue @@ -1,7 +1,7 @@ @@ -522,4 +725,18 @@ onMounted(() => { .query-form { margin-bottom: 20px; } + +/* 详情对话框样式优化 */ +:deep(.el-descriptions) { + margin-top: 10px; +} + +:deep(.el-descriptions__label) { + font-weight: 600; + background-color: #fafafa; +} + +:deep(.el-descriptions__content) { + color: #333; +}