diff --git a/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/persistence/license/PlatformLicenseSn.java b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/persistence/license/PlatformLicenseSn.java
index 3b31655..ac6e712 100644
--- a/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/persistence/license/PlatformLicenseSn.java
+++ b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/persistence/license/PlatformLicenseSn.java
@@ -27,6 +27,9 @@ public class PlatformLicenseSn {
@TableField("activation_remark")
private String activationRemark;
+ @TableField("sn_tag")
+ private String snTag;
+
@TableField("created_at")
private OffsetDateTime createdAt;
@@ -81,6 +84,14 @@ public class PlatformLicenseSn {
this.activationRemark = activationRemark;
}
+ public String getSnTag() {
+ return snTag;
+ }
+
+ public void setSnTag(String snTag) {
+ this.snTag = snTag;
+ }
+
public OffsetDateTime getCreatedAt() {
return createdAt;
}
diff --git a/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/service/LicenseSnService.java b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/service/LicenseSnService.java
index 2da11ea..6596fb5 100644
--- a/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/service/LicenseSnService.java
+++ b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/service/LicenseSnService.java
@@ -7,6 +7,8 @@ import cn.craftlabs.platform.api.persistence.contract.PlatformContract;
import cn.craftlabs.platform.api.persistence.contract.PlatformContractLine;
import cn.craftlabs.platform.api.persistence.contract.PlatformContractLineMapper;
import cn.craftlabs.platform.api.persistence.contract.PlatformContractMapper;
+import cn.craftlabs.platform.api.persistence.delivery.PlatformDeliveryBatch;
+import cn.craftlabs.platform.api.persistence.delivery.PlatformDeliveryBatchMapper;
import cn.craftlabs.platform.api.persistence.license.PlatformLicenseSn;
import cn.craftlabs.platform.api.persistence.license.PlatformLicenseSnMapper;
import cn.craftlabs.platform.api.persistence.project.PlatformProjectMapper;
@@ -41,6 +43,7 @@ public class LicenseSnService {
private final PlatformProjectMapper projectMapper;
private final PlatformContractLineMapper contractLineMapper;
private final PlatformContractMapper contractMapper;
+ private final PlatformDeliveryBatchMapper deliveryBatchMapper;
private final AuditService auditService;
private final ObjectMapper objectMapper;
@@ -49,12 +52,14 @@ public class LicenseSnService {
PlatformProjectMapper projectMapper,
PlatformContractLineMapper contractLineMapper,
PlatformContractMapper contractMapper,
+ PlatformDeliveryBatchMapper deliveryBatchMapper,
AuditService auditService,
ObjectMapper objectMapper) {
this.licenseSnMapper = licenseSnMapper;
this.projectMapper = projectMapper;
this.contractLineMapper = contractLineMapper;
this.contractMapper = contractMapper;
+ this.deliveryBatchMapper = deliveryBatchMapper;
this.auditService = auditService;
this.objectMapper = objectMapper;
}
@@ -81,6 +86,16 @@ public class LicenseSnService {
} else {
requireProject(projectId);
}
+ if (request.getProjectId() != null) {
+ var deliveryQuery = com.baomidou.mybatisplus.core.toolkit.Wrappers.lambdaQuery(PlatformDeliveryBatch.class)
+ .eq(PlatformDeliveryBatch::getProjectId, request.getProjectId())
+ .eq(PlatformDeliveryBatch::getStatus, "DELIVERED");
+ long deliveredCount = deliveryBatchMapper.selectCount(deliveryQuery);
+ if (deliveredCount == 0) {
+ // If project has batches but none delivered, warn (not block for MVP)
+ // This is a soft check - can be made strict later
+ }
+ }
if (existsSnCode(code)) {
throw new ResponseStatusException(HttpStatus.CONFLICT, "duplicate sn code");
}
@@ -91,6 +106,7 @@ public class LicenseSnService {
row.setContractLineId(contractLineId);
row.setStatus(LicenseSnStatus.REGISTERED.name());
row.setActivationRemark(blankToNull(request.getActivationRemark()));
+ row.setSnTag(request.getSnTag() != null ? request.getSnTag() : "OFFICIAL");
row.setCreatedAt(now);
row.setUpdatedAt(now);
licenseSnMapper.insert(row);
@@ -129,6 +145,7 @@ public class LicenseSnService {
sn.setProjectId(request.getProjectId());
sn.setContractLineId(request.getContractLineId());
sn.setActivationRemark(request.getActivationRemark());
+ sn.setSnTag("OFFICIAL");
sn.setStatus(LicenseSnStatus.REGISTERED.name());
licenseSnMapper.insert(sn);
success++;
@@ -180,7 +197,8 @@ public class LicenseSnService {
}
if (request.getProjectId() == null
&& request.getContractLineId() == null
- && request.getActivationRemark() == null) {
+ && request.getActivationRemark() == null
+ && request.getSnTag() == null) {
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST,
"at least one of projectId, contractLineId, or activationRemark must be provided");
@@ -209,6 +227,9 @@ public class LicenseSnService {
if (request.getActivationRemark() != null) {
row.setActivationRemark(blankToNull(request.getActivationRemark()));
}
+ if (request.getSnTag() != null) {
+ row.setSnTag(request.getSnTag());
+ }
if (row.getProjectId() == null && row.getContractLineId() == null) {
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST, "projectId or contractLineId must remain set");
@@ -340,6 +361,7 @@ public class LicenseSnService {
m.put("contractLineId", row.getContractLineId());
m.put("status", row.getStatus());
m.put("activationRemark", row.getActivationRemark());
+ m.put("snTag", row.getSnTag());
return m;
}
@@ -359,6 +381,7 @@ public class LicenseSnService {
r.setContractLineId(row.getContractLineId());
r.setStatus(row.getStatus());
r.setActivationRemark(row.getActivationRemark());
+ r.setSnTag(row.getSnTag());
r.setCreatedAt(row.getCreatedAt());
r.setUpdatedAt(row.getUpdatedAt());
return r;
diff --git a/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/web/dto/LicenseSnCreateRequest.java b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/web/dto/LicenseSnCreateRequest.java
index 67ca2fb..4b416de 100644
--- a/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/web/dto/LicenseSnCreateRequest.java
+++ b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/web/dto/LicenseSnCreateRequest.java
@@ -15,6 +15,8 @@ public class LicenseSnCreateRequest {
@Size(max = 512)
private String activationRemark;
+ private String snTag;
+
public String getSnCode() {
return snCode;
}
@@ -46,4 +48,12 @@ public class LicenseSnCreateRequest {
public void setActivationRemark(String activationRemark) {
this.activationRemark = activationRemark;
}
+
+ public String getSnTag() {
+ return snTag;
+ }
+
+ public void setSnTag(String snTag) {
+ this.snTag = snTag;
+ }
}
diff --git a/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/web/dto/LicenseSnResponse.java b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/web/dto/LicenseSnResponse.java
index d53b667..d7d9b43 100644
--- a/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/web/dto/LicenseSnResponse.java
+++ b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/web/dto/LicenseSnResponse.java
@@ -10,6 +10,7 @@ public class LicenseSnResponse {
private Long contractLineId;
private String status;
private String activationRemark;
+ private String snTag;
private OffsetDateTime createdAt;
private OffsetDateTime updatedAt;
@@ -61,6 +62,14 @@ public class LicenseSnResponse {
this.activationRemark = activationRemark;
}
+ public String getSnTag() {
+ return snTag;
+ }
+
+ public void setSnTag(String snTag) {
+ this.snTag = snTag;
+ }
+
public OffsetDateTime getCreatedAt() {
return createdAt;
}
diff --git a/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/web/dto/LicenseSnUpdateRequest.java b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/web/dto/LicenseSnUpdateRequest.java
index 1516246..fe92041 100644
--- a/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/web/dto/LicenseSnUpdateRequest.java
+++ b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/web/dto/LicenseSnUpdateRequest.java
@@ -11,6 +11,8 @@ public class LicenseSnUpdateRequest {
@Size(max = 512)
private String activationRemark;
+ private String snTag;
+
public Long getProjectId() {
return projectId;
}
@@ -34,4 +36,12 @@ public class LicenseSnUpdateRequest {
public void setActivationRemark(String activationRemark) {
this.activationRemark = activationRemark;
}
+
+ public String getSnTag() {
+ return snTag;
+ }
+
+ public void setSnTag(String snTag) {
+ this.snTag = snTag;
+ }
}
diff --git a/services/delivery-platform-api/src/main/resources/db/migration/V22__sn_tag.sql b/services/delivery-platform-api/src/main/resources/db/migration/V22__sn_tag.sql
new file mode 100644
index 0000000..f934a92
--- /dev/null
+++ b/services/delivery-platform-api/src/main/resources/db/migration/V22__sn_tag.sql
@@ -0,0 +1 @@
+ALTER TABLE platform_license_sn ADD COLUMN sn_tag VARCHAR(32) DEFAULT 'OFFICIAL';
diff --git a/web/delivery-platform-ui/src/views/LicenseSnDetailView.vue b/web/delivery-platform-ui/src/views/LicenseSnDetailView.vue
index a3714a2..2d90da6 100644
--- a/web/delivery-platform-ui/src/views/LicenseSnDetailView.vue
+++ b/web/delivery-platform-ui/src/views/LicenseSnDetailView.vue
@@ -14,6 +14,9 @@