diff --git a/README.en.md b/README.en.md
index c4dd558..86fb5e8 100644
--- a/README.en.md
+++ b/README.en.md
@@ -1,7 +1,7 @@
# petstore-backend
#### Description
-宠伴生活馆
+宠小它
#### Software Architecture
Software architecture description
diff --git a/README.md b/README.md
index 2d1d3ec..7747d61 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# petstore-backend
#### 介绍
-宠伴生活馆
+宠小它
#### 软件架构
软件架构说明
diff --git a/pom.xml b/pom.xml
index 12db726..a5a927e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -16,7 +16,7 @@
1.0.0
jar
petstore-backend
- 宠伴生活馆 后端服务
+ 宠小它 后端服务|智慧宠物门店服务系统
17
diff --git a/src/main/java/com/petstore/controller/AppointmentController.java b/src/main/java/com/petstore/controller/AppointmentController.java
index 402b92e..8691e99 100644
--- a/src/main/java/com/petstore/controller/AppointmentController.java
+++ b/src/main/java/com/petstore/controller/AppointmentController.java
@@ -117,21 +117,13 @@ public class AppointmentController {
public Map start(@RequestBody Map params) {
Long appointmentId = Long.valueOf(params.get("appointmentId").toString());
Long staffUserId = Long.valueOf(params.get("staffUserId").toString());
- Appointment updated = appointmentService.startService(appointmentId, staffUserId);
- if (updated != null) {
- return Map.of("code", 200, "message", "已开始服务", "data", updated);
- }
- return Map.of("code", 404, "message", "预约不存在");
+ return appointmentService.startService(appointmentId, staffUserId);
}
- /** 更新预约状态 */
+ /** 更新预约状态(合法迁移见 AppointmentService.transitionStatus) */
@PutMapping("/status")
public Map updateStatus(@RequestParam Long id, @RequestParam String status) {
- Appointment updated = appointmentService.updateStatus(id, status);
- if (updated != null) {
- return Map.of("code", 200, "message", "更新成功", "data", updated);
- }
- return Map.of("code", 404, "message", "预约不存在");
+ return appointmentService.transitionStatus(id, status);
}
@DeleteMapping("/delete")
diff --git a/src/main/java/com/petstore/controller/ReportController.java b/src/main/java/com/petstore/controller/ReportController.java
index e103c23..497f792 100644
--- a/src/main/java/com/petstore/controller/ReportController.java
+++ b/src/main/java/com/petstore/controller/ReportController.java
@@ -5,6 +5,8 @@ import com.petstore.entity.Report;
import com.petstore.entity.ReportImage;
import com.petstore.entity.Store;
import com.petstore.entity.User;
+import com.petstore.entity.Appointment;
+import com.petstore.service.AppointmentService;
import com.petstore.service.ReportHighlightVideoService;
import com.petstore.service.ReportService;
import com.petstore.service.ReportTestimonialService;
@@ -13,6 +15,8 @@ import com.petstore.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
@@ -24,7 +28,9 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor
@CrossOrigin
public class ReportController {
+ private static final Logger log = LoggerFactory.getLogger(ReportController.class);
private final ReportService reportService;
+ private final AppointmentService appointmentService;
private final StoreService storeService;
private final ReportHighlightVideoService reportHighlightVideoService;
private final ReportTestimonialService reportTestimonialService;
@@ -92,9 +98,39 @@ public class ReportController {
return reportHighlightVideoService.requestGenerate(reportId, operatorUserId, role, composeMode);
}
+ /**
+ * P0 埋点:报告打开(首访/再次)。当前写入应用日志,便于日志平台统计;不落库。
+ */
+ @PostMapping("/open-track")
+ public Map trackReportOpen(@RequestBody Map body) {
+ Object rawTok = body == null ? null : body.get("token");
+ String token = rawTok == null ? "" : rawTok.toString().trim();
+ if (token.isEmpty()) {
+ return Map.of("code", 400, "message", "token 必填");
+ }
+ String visitType = body.get("visitType") == null ? "" : body.get("visitType").toString();
+ String prefix = token.length() > 16 ? token.substring(0, 16) + "…" : token;
+ log.info("report_open tokenPrefix={} visitType={}", prefix, visitType);
+ return Map.of("code", 200);
+ }
+
@PostMapping("/create")
public Map create(@RequestBody Report report) {
System.out.println(">>> Report create received: appointmentId=" + report.getAppointmentId() + ", userId=" + report.getUserId() + ", images count=" + (report.getImages() == null ? 0 : report.getImages().size()));
+ if (report.getAppointmentId() != null) {
+ Appointment appt = appointmentService.getById(report.getAppointmentId());
+ if (appt == null) {
+ return Map.of("code", 404, "message", "关联预约不存在", "bizCode", "NOT_FOUND");
+ }
+ String st = appt.getStatus() == null ? "" : appt.getStatus().trim().toLowerCase();
+ if (!"doing".equals(st)) {
+ return Map.of(
+ "code", 400,
+ "message", "请先开始服务后再提交报告",
+ "bizCode", "INVALID_STATUS"
+ );
+ }
+ }
Report created = reportService.create(report);
Map result = new HashMap<>();
diff --git a/src/main/java/com/petstore/controller/SmsController.java b/src/main/java/com/petstore/controller/SmsController.java
index 3bb2375..ebebc75 100644
--- a/src/main/java/com/petstore/controller/SmsController.java
+++ b/src/main/java/com/petstore/controller/SmsController.java
@@ -35,7 +35,7 @@ public class SmsController {
// 阿里云示例: dysmsapi.aliyuncs.com
// 腾讯云示例: sms.tencentcloudapi.com
- System.out.println("【宠伴生活馆】验证码:" + code + ",您正在登录,5分钟内有效。");
+ System.out.println("【宠小它】验证码:" + code + ",您正在登录,5分钟内有效。");
result.put("code", 200);
result.put("message", "发送成功");
diff --git a/src/main/java/com/petstore/service/AppointmentService.java b/src/main/java/com/petstore/service/AppointmentService.java
index ff0e10e..e4aa009 100644
--- a/src/main/java/com/petstore/service/AppointmentService.java
+++ b/src/main/java/com/petstore/service/AppointmentService.java
@@ -177,26 +177,72 @@ public class AppointmentService {
return ok;
}
- public Appointment updateStatus(Long id, String status) {
+ /**
+ * 预约状态迁移(PUT /appointment/status 等)。
+ * 允许:new→cancel、doing→done;new→doing 请走 {@link #startService}。
+ */
+ public Map transitionStatus(Long id, String nextStatus) {
Appointment appointment = appointmentMapper.findByIdAndDeletedFalse(id).orElse(null);
- if (appointment != null) {
- appointment.setStatus(status);
- appointment.setUpdateTime(LocalDateTime.now());
- return appointmentMapper.save(appointment);
+ if (appointment == null) {
+ return Map.of("code", 404, "message", "预约不存在", "bizCode", "NOT_FOUND");
}
- return null;
+ if (nextStatus == null || nextStatus.isBlank()) {
+ return Map.of("code", 400, "message", "状态不能为空", "bizCode", "INVALID_STATUS");
+ }
+ String to = nextStatus.trim().toLowerCase();
+ String from = appointment.getStatus() == null ? "" : appointment.getStatus().trim().toLowerCase();
+
+ if ("cancel".equals(to)) {
+ if (!"new".equals(from)) {
+ return Map.of(
+ "code", 400,
+ "message", "仅待开始的预约可取消",
+ "bizCode", "CANCEL_NOT_ALLOWED"
+ );
+ }
+ } else if ("done".equals(to)) {
+ if (!"doing".equals(from)) {
+ return Map.of(
+ "code", 400,
+ "message", "仅进行中的预约可标记为已完成",
+ "bizCode", "INVALID_STATUS"
+ );
+ }
+ } else if ("doing".equals(to)) {
+ return Map.of(
+ "code", 400,
+ "message", "请使用「开始服务」接口将预约置为进行中",
+ "bizCode", "USE_START_ENDPOINT"
+ );
+ } else {
+ return Map.of("code", 400, "message", "不支持的状态变更", "bizCode", "INVALID_STATUS");
+ }
+
+ appointment.setStatus(to);
+ appointment.setUpdateTime(LocalDateTime.now());
+ Appointment saved = appointmentMapper.save(appointment);
+ return Map.of("code", 200, "message", "更新成功", "data", saved);
}
- /** 开始服务:状态变为进行中,同时指定技师为当前用户 */
- public Appointment startService(Long appointmentId, Long staffUserId) {
+ /** 开始服务:仅 new → doing,并指定技师 */
+ public Map startService(Long appointmentId, Long staffUserId) {
Appointment appointment = appointmentMapper.findByIdAndDeletedFalse(appointmentId).orElse(null);
- if (appointment != null) {
- appointment.setStatus("doing");
- appointment.setAssignedUserId(staffUserId);
- appointment.setUpdateTime(LocalDateTime.now());
- return appointmentMapper.save(appointment);
+ if (appointment == null) {
+ return Map.of("code", 404, "message", "预约不存在", "bizCode", "NOT_FOUND");
}
- return null;
+ String from = appointment.getStatus() == null ? "" : appointment.getStatus().trim().toLowerCase();
+ if (!"new".equals(from)) {
+ return Map.of(
+ "code", 400,
+ "message", "仅待开始的预约可开始服务",
+ "bizCode", "INVALID_STATUS"
+ );
+ }
+ appointment.setStatus("doing");
+ appointment.setAssignedUserId(staffUserId);
+ appointment.setUpdateTime(LocalDateTime.now());
+ Appointment saved = appointmentMapper.save(appointment);
+ return Map.of("code", 200, "message", "已开始服务", "data", saved);
}
public boolean softDelete(Long id) {
diff --git a/src/main/java/com/petstore/service/ReportService.java b/src/main/java/com/petstore/service/ReportService.java
index 7bff2bb..1161d7a 100644
--- a/src/main/java/com/petstore/service/ReportService.java
+++ b/src/main/java/com/petstore/service/ReportService.java
@@ -41,10 +41,12 @@ public class ReportService {
report.setStaffName(staff.getName());
}
}
- // 填写完报告,自动标记预约为已完成
- appt.setStatus("done");
- appt.setUpdateTime(LocalDateTime.now());
- appointmentMapper.save(appt);
+ // 填写完报告:仅进行中 → 已完成(与预约状态机一致)
+ if ("doing".equalsIgnoreCase(appt.getStatus())) {
+ appt.setStatus("done");
+ appt.setUpdateTime(LocalDateTime.now());
+ appointmentMapper.save(appt);
+ }
}
}
// 如果预约没分配技师,则用当前操作人