mirror of
https://github.com/hpd840321/craftlabs-authorization-sdk.git
synced 2026-06-09 10:00:30 +08:00
feat(platform): I4 delivery batches, lines, and license SN APIs
Add Flyway V4 tables, delivery-batches and license-sns endpoints with validation, audit actions, controller tests, and OpenAPI snapshot update. Made-with: Cursor
This commit is contained in:
+11
@@ -10,5 +10,16 @@ public final class AuditActions {
|
||||
public static final String CONTRACT_LINE_DELETED = "CONTRACT_LINE_DELETED";
|
||||
public static final String CONTRACT_STATUS_CHANGED = "CONTRACT_STATUS_CHANGED";
|
||||
|
||||
public static final String DELIVERY_BATCH_CREATED = "DELIVERY_BATCH_CREATED";
|
||||
public static final String DELIVERY_BATCH_UPDATED = "DELIVERY_BATCH_UPDATED";
|
||||
public static final String DELIVERY_BATCH_STATUS_CHANGED = "DELIVERY_BATCH_STATUS_CHANGED";
|
||||
public static final String DELIVERY_LINE_ADDED = "DELIVERY_LINE_ADDED";
|
||||
public static final String DELIVERY_LINE_UPDATED = "DELIVERY_LINE_UPDATED";
|
||||
public static final String DELIVERY_LINE_DELETED = "DELIVERY_LINE_DELETED";
|
||||
|
||||
public static final String LICENSE_SN_CREATED = "LICENSE_SN_CREATED";
|
||||
public static final String LICENSE_SN_UPDATED = "LICENSE_SN_UPDATED";
|
||||
public static final String LICENSE_SN_STATUS_CHANGED = "LICENSE_SN_STATUS_CHANGED";
|
||||
|
||||
private AuditActions() {}
|
||||
}
|
||||
|
||||
+2
@@ -3,6 +3,8 @@ package cn.craftlabs.platform.api.audit;
|
||||
public final class AuditEntityTypes {
|
||||
|
||||
public static final String CONTRACT = "CONTRACT";
|
||||
public static final String DELIVERY_BATCH = "DELIVERY_BATCH";
|
||||
public static final String LICENSE_SN = "LICENSE_SN";
|
||||
|
||||
private AuditEntityTypes() {}
|
||||
}
|
||||
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
package cn.craftlabs.platform.api.delivery;
|
||||
|
||||
import cn.craftlabs.platform.api.service.DeliveryBatchService;
|
||||
import cn.craftlabs.platform.api.web.dto.DeliveryBatchCreateRequest;
|
||||
import cn.craftlabs.platform.api.web.dto.DeliveryBatchResponse;
|
||||
import cn.craftlabs.platform.api.web.dto.DeliveryBatchStatusPatchRequest;
|
||||
import cn.craftlabs.platform.api.web.dto.DeliveryBatchUpdateRequest;
|
||||
import cn.craftlabs.platform.api.web.dto.DeliveryLineRequest;
|
||||
import cn.craftlabs.platform.api.web.dto.DeliveryLineResponse;
|
||||
import cn.craftlabs.platform.api.web.dto.PageResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PatchMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/delivery-batches")
|
||||
@Validated
|
||||
public class DeliveryBatchController {
|
||||
|
||||
private final DeliveryBatchService deliveryBatchService;
|
||||
|
||||
public DeliveryBatchController(DeliveryBatchService deliveryBatchService) {
|
||||
this.deliveryBatchService = deliveryBatchService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public PageResponse<DeliveryBatchResponse> list(
|
||||
@RequestParam(value = "page", defaultValue = "0") @Min(0) int page,
|
||||
@RequestParam(value = "size", defaultValue = "20") @Min(1) @Max(200) int size,
|
||||
@RequestParam(value = "projectId", required = false) Long projectId,
|
||||
@RequestParam(value = "contractId", required = false) Long contractId,
|
||||
@RequestParam(value = "keyword", required = false) String keyword) {
|
||||
return deliveryBatchService.page(page, size, projectId, contractId, keyword);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public DeliveryBatchResponse create(@Valid @RequestBody DeliveryBatchCreateRequest request) {
|
||||
return deliveryBatchService.create(request);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public DeliveryBatchResponse get(@PathVariable("id") long id) {
|
||||
return deliveryBatchService.getById(id);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public DeliveryBatchResponse update(
|
||||
@PathVariable("id") long id, @Valid @RequestBody DeliveryBatchUpdateRequest request) {
|
||||
return deliveryBatchService.update(id, request);
|
||||
}
|
||||
|
||||
@PatchMapping("/{id}/status")
|
||||
public DeliveryBatchResponse patchStatus(
|
||||
@PathVariable("id") long id, @Valid @RequestBody DeliveryBatchStatusPatchRequest request) {
|
||||
return deliveryBatchService.patchStatus(id, request);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}/lines")
|
||||
public List<DeliveryLineResponse> listLines(@PathVariable("id") long batchId) {
|
||||
return deliveryBatchService.listLines(batchId);
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/lines")
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public DeliveryLineResponse addLine(
|
||||
@PathVariable("id") long batchId, @Valid @RequestBody DeliveryLineRequest request) {
|
||||
return deliveryBatchService.addLine(batchId, request);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}/lines/{lineId}")
|
||||
public DeliveryLineResponse updateLine(
|
||||
@PathVariable("id") long batchId,
|
||||
@PathVariable("lineId") long lineId,
|
||||
@Valid @RequestBody DeliveryLineRequest request) {
|
||||
return deliveryBatchService.updateLine(batchId, lineId, request);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}/lines/{lineId}")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
public void deleteLine(
|
||||
@PathVariable("id") long batchId, @PathVariable("lineId") long lineId) {
|
||||
deliveryBatchService.deleteLine(batchId, lineId);
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package cn.craftlabs.platform.api.domain;
|
||||
|
||||
/** M3:交付批次状态(P0)。 */
|
||||
public enum DeliveryBatchStatus {
|
||||
PENDING,
|
||||
DELIVERED,
|
||||
CANCELLED;
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package cn.craftlabs.platform.api.domain;
|
||||
|
||||
/** M4:SN 生命周期状态(P0 子集)。 */
|
||||
public enum LicenseSnStatus {
|
||||
REGISTERED,
|
||||
ISSUED,
|
||||
ACTIVATED,
|
||||
SUSPENDED,
|
||||
REVOKED;
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package cn.craftlabs.platform.api.license;
|
||||
|
||||
import cn.craftlabs.platform.api.service.LicenseSnService;
|
||||
import cn.craftlabs.platform.api.web.dto.LicenseSnCreateRequest;
|
||||
import cn.craftlabs.platform.api.web.dto.LicenseSnResponse;
|
||||
import cn.craftlabs.platform.api.web.dto.LicenseSnStatusPatchRequest;
|
||||
import cn.craftlabs.platform.api.web.dto.LicenseSnUpdateRequest;
|
||||
import cn.craftlabs.platform.api.web.dto.PageResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PatchMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/license-sns")
|
||||
@Validated
|
||||
public class LicenseSnController {
|
||||
|
||||
private final LicenseSnService licenseSnService;
|
||||
|
||||
public LicenseSnController(LicenseSnService licenseSnService) {
|
||||
this.licenseSnService = licenseSnService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public PageResponse<LicenseSnResponse> list(
|
||||
@RequestParam(value = "page", defaultValue = "0") @Min(0) int page,
|
||||
@RequestParam(value = "size", defaultValue = "20") @Min(1) @Max(200) int size,
|
||||
@RequestParam(value = "projectId", required = false) Long projectId,
|
||||
@RequestParam(value = "keyword", required = false) String keyword,
|
||||
@RequestParam(value = "status", required = false) String status) {
|
||||
return licenseSnService.page(page, size, projectId, keyword, status);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public LicenseSnResponse create(@Valid @RequestBody LicenseSnCreateRequest request) {
|
||||
return licenseSnService.create(request);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public LicenseSnResponse get(@PathVariable("id") long id) {
|
||||
return licenseSnService.getById(id);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public LicenseSnResponse update(
|
||||
@PathVariable("id") long id, @Valid @RequestBody LicenseSnUpdateRequest request) {
|
||||
return licenseSnService.update(id, request);
|
||||
}
|
||||
|
||||
@PatchMapping("/{id}/status")
|
||||
public LicenseSnResponse patchStatus(
|
||||
@PathVariable("id") long id, @Valid @RequestBody LicenseSnStatusPatchRequest request) {
|
||||
return licenseSnService.patchStatus(id, request);
|
||||
}
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
package cn.craftlabs.platform.api.persistence.delivery;
|
||||
|
||||
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 java.time.LocalDate;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
@TableName("platform_delivery_batch")
|
||||
public class PlatformDeliveryBatch {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
@TableField("project_id")
|
||||
private Long projectId;
|
||||
|
||||
@TableField("contract_id")
|
||||
private Long contractId;
|
||||
|
||||
@TableField("batch_code")
|
||||
private String batchCode;
|
||||
|
||||
@TableField("planned_delivery_date")
|
||||
private LocalDate plannedDeliveryDate;
|
||||
|
||||
private String status;
|
||||
|
||||
@TableField("finished_at")
|
||||
private OffsetDateTime finishedAt;
|
||||
|
||||
private String remarks;
|
||||
|
||||
@TableField("created_at")
|
||||
private OffsetDateTime createdAt;
|
||||
|
||||
@TableField("updated_at")
|
||||
private OffsetDateTime updatedAt;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Long getProjectId() {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
public void setProjectId(Long projectId) {
|
||||
this.projectId = projectId;
|
||||
}
|
||||
|
||||
public Long getContractId() {
|
||||
return contractId;
|
||||
}
|
||||
|
||||
public void setContractId(Long contractId) {
|
||||
this.contractId = contractId;
|
||||
}
|
||||
|
||||
public String getBatchCode() {
|
||||
return batchCode;
|
||||
}
|
||||
|
||||
public void setBatchCode(String batchCode) {
|
||||
this.batchCode = batchCode;
|
||||
}
|
||||
|
||||
public LocalDate getPlannedDeliveryDate() {
|
||||
return plannedDeliveryDate;
|
||||
}
|
||||
|
||||
public void setPlannedDeliveryDate(LocalDate plannedDeliveryDate) {
|
||||
this.plannedDeliveryDate = plannedDeliveryDate;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public OffsetDateTime getFinishedAt() {
|
||||
return finishedAt;
|
||||
}
|
||||
|
||||
public void setFinishedAt(OffsetDateTime finishedAt) {
|
||||
this.finishedAt = finishedAt;
|
||||
}
|
||||
|
||||
public String getRemarks() {
|
||||
return remarks;
|
||||
}
|
||||
|
||||
public void setRemarks(String remarks) {
|
||||
this.remarks = remarks;
|
||||
}
|
||||
|
||||
public OffsetDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public OffsetDateTime getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(OffsetDateTime updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package cn.craftlabs.platform.api.persistence.delivery;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface PlatformDeliveryBatchMapper extends BaseMapper<PlatformDeliveryBatch> {}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
package cn.craftlabs.platform.api.persistence.delivery;
|
||||
|
||||
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 java.math.BigDecimal;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
@TableName("platform_delivery_line")
|
||||
public class PlatformDeliveryLine {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
@TableField("batch_id")
|
||||
private Long batchId;
|
||||
|
||||
@TableField("sort_order")
|
||||
private Integer sortOrder;
|
||||
|
||||
private String description;
|
||||
|
||||
private BigDecimal quantity;
|
||||
|
||||
@TableField("contract_line_id")
|
||||
private Long contractLineId;
|
||||
|
||||
@TableField("created_at")
|
||||
private OffsetDateTime createdAt;
|
||||
|
||||
@TableField("updated_at")
|
||||
private OffsetDateTime updatedAt;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Long getBatchId() {
|
||||
return batchId;
|
||||
}
|
||||
|
||||
public void setBatchId(Long batchId) {
|
||||
this.batchId = batchId;
|
||||
}
|
||||
|
||||
public Integer getSortOrder() {
|
||||
return sortOrder;
|
||||
}
|
||||
|
||||
public void setSortOrder(Integer sortOrder) {
|
||||
this.sortOrder = sortOrder;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public BigDecimal getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public void setQuantity(BigDecimal quantity) {
|
||||
this.quantity = quantity;
|
||||
}
|
||||
|
||||
public Long getContractLineId() {
|
||||
return contractLineId;
|
||||
}
|
||||
|
||||
public void setContractLineId(Long contractLineId) {
|
||||
this.contractLineId = contractLineId;
|
||||
}
|
||||
|
||||
public OffsetDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public OffsetDateTime getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(OffsetDateTime updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package cn.craftlabs.platform.api.persistence.delivery;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface PlatformDeliveryLineMapper extends BaseMapper<PlatformDeliveryLine> {}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
package cn.craftlabs.platform.api.persistence.license;
|
||||
|
||||
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 java.time.OffsetDateTime;
|
||||
|
||||
@TableName("platform_license_sn")
|
||||
public class PlatformLicenseSn {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
@TableField("sn_code")
|
||||
private String snCode;
|
||||
|
||||
@TableField("project_id")
|
||||
private Long projectId;
|
||||
|
||||
@TableField("contract_line_id")
|
||||
private Long contractLineId;
|
||||
|
||||
private String status;
|
||||
|
||||
@TableField("activation_remark")
|
||||
private String activationRemark;
|
||||
|
||||
@TableField("created_at")
|
||||
private OffsetDateTime createdAt;
|
||||
|
||||
@TableField("updated_at")
|
||||
private OffsetDateTime updatedAt;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getSnCode() {
|
||||
return snCode;
|
||||
}
|
||||
|
||||
public void setSnCode(String snCode) {
|
||||
this.snCode = snCode;
|
||||
}
|
||||
|
||||
public Long getProjectId() {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
public void setProjectId(Long projectId) {
|
||||
this.projectId = projectId;
|
||||
}
|
||||
|
||||
public Long getContractLineId() {
|
||||
return contractLineId;
|
||||
}
|
||||
|
||||
public void setContractLineId(Long contractLineId) {
|
||||
this.contractLineId = contractLineId;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getActivationRemark() {
|
||||
return activationRemark;
|
||||
}
|
||||
|
||||
public void setActivationRemark(String activationRemark) {
|
||||
this.activationRemark = activationRemark;
|
||||
}
|
||||
|
||||
public OffsetDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public OffsetDateTime getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(OffsetDateTime updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package cn.craftlabs.platform.api.persistence.license;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface PlatformLicenseSnMapper extends BaseMapper<PlatformLicenseSn> {}
|
||||
+453
@@ -0,0 +1,453 @@
|
||||
package cn.craftlabs.platform.api.service;
|
||||
|
||||
import cn.craftlabs.platform.api.audit.AuditActions;
|
||||
import cn.craftlabs.platform.api.audit.AuditEntityTypes;
|
||||
import cn.craftlabs.platform.api.domain.DeliveryBatchStatus;
|
||||
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.delivery.PlatformDeliveryLine;
|
||||
import cn.craftlabs.platform.api.persistence.delivery.PlatformDeliveryLineMapper;
|
||||
import cn.craftlabs.platform.api.persistence.project.PlatformProject;
|
||||
import cn.craftlabs.platform.api.persistence.project.PlatformProjectMapper;
|
||||
import cn.craftlabs.platform.api.web.dto.DeliveryBatchCreateRequest;
|
||||
import cn.craftlabs.platform.api.web.dto.DeliveryBatchResponse;
|
||||
import cn.craftlabs.platform.api.web.dto.DeliveryBatchStatusPatchRequest;
|
||||
import cn.craftlabs.platform.api.web.dto.DeliveryBatchUpdateRequest;
|
||||
import cn.craftlabs.platform.api.web.dto.DeliveryLineRequest;
|
||||
import cn.craftlabs.platform.api.web.dto.DeliveryLineResponse;
|
||||
import cn.craftlabs.platform.api.web.dto.PageResponse;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class DeliveryBatchService {
|
||||
|
||||
private final PlatformDeliveryBatchMapper batchMapper;
|
||||
private final PlatformDeliveryLineMapper lineMapper;
|
||||
private final PlatformProjectMapper projectMapper;
|
||||
private final PlatformContractMapper contractMapper;
|
||||
private final PlatformContractLineMapper contractLineMapper;
|
||||
private final AuditService auditService;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public DeliveryBatchService(
|
||||
PlatformDeliveryBatchMapper batchMapper,
|
||||
PlatformDeliveryLineMapper lineMapper,
|
||||
PlatformProjectMapper projectMapper,
|
||||
PlatformContractMapper contractMapper,
|
||||
PlatformContractLineMapper contractLineMapper,
|
||||
AuditService auditService,
|
||||
ObjectMapper objectMapper) {
|
||||
this.batchMapper = batchMapper;
|
||||
this.lineMapper = lineMapper;
|
||||
this.projectMapper = projectMapper;
|
||||
this.contractMapper = contractMapper;
|
||||
this.contractLineMapper = contractLineMapper;
|
||||
this.auditService = auditService;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public DeliveryBatchResponse create(DeliveryBatchCreateRequest request) {
|
||||
requireProject(request.getProjectId());
|
||||
Long contractId = request.getContractId();
|
||||
if (contractId != null) {
|
||||
PlatformContract c = requireContract(contractId);
|
||||
if (!c.getProjectId().equals(request.getProjectId())) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST, "contract.projectId must match batch projectId");
|
||||
}
|
||||
}
|
||||
String code = request.getBatchCode().trim();
|
||||
if (existsBatchCode(code)) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "duplicate batch code");
|
||||
}
|
||||
OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC);
|
||||
PlatformDeliveryBatch b = new PlatformDeliveryBatch();
|
||||
b.setProjectId(request.getProjectId());
|
||||
b.setContractId(contractId);
|
||||
b.setBatchCode(code);
|
||||
b.setPlannedDeliveryDate(parsePlannedDateOrNull(request.getPlannedDeliveryDate()));
|
||||
b.setStatus(DeliveryBatchStatus.PENDING.name());
|
||||
b.setRemarks(blankToNull(request.getRemarks()));
|
||||
b.setCreatedAt(now);
|
||||
b.setUpdatedAt(now);
|
||||
batchMapper.insert(b);
|
||||
auditService.record(
|
||||
AuditEntityTypes.DELIVERY_BATCH,
|
||||
b.getId(),
|
||||
AuditActions.DELIVERY_BATCH_CREATED,
|
||||
null,
|
||||
null,
|
||||
toJson(batchSnapshot(b)));
|
||||
return toResponse(b);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public PageResponse<DeliveryBatchResponse> page(
|
||||
int page, int size, Long projectId, Long contractId, String keyword) {
|
||||
String kw = StringUtils.hasText(keyword) ? keyword.trim() : null;
|
||||
LambdaQueryWrapper<PlatformDeliveryBatch> q =
|
||||
Wrappers.lambdaQuery(PlatformDeliveryBatch.class)
|
||||
.eq(projectId != null, PlatformDeliveryBatch::getProjectId, projectId)
|
||||
.eq(contractId != null, PlatformDeliveryBatch::getContractId, contractId)
|
||||
.like(kw != null, PlatformDeliveryBatch::getBatchCode, kw)
|
||||
.orderByDesc(PlatformDeliveryBatch::getId);
|
||||
Page<PlatformDeliveryBatch> mpPage = new Page<>(page + 1L, size);
|
||||
batchMapper.selectPage(mpPage, q);
|
||||
List<DeliveryBatchResponse> content =
|
||||
mpPage.getRecords().stream().map(this::toResponse).collect(Collectors.toList());
|
||||
return new PageResponse<>(content, mpPage.getTotal(), page, size);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public DeliveryBatchResponse getById(long id) {
|
||||
PlatformDeliveryBatch b = requireBatch(id);
|
||||
DeliveryBatchResponse r = toResponse(b);
|
||||
r.setLines(listLines(id));
|
||||
return r;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public DeliveryBatchResponse update(long id, DeliveryBatchUpdateRequest request) {
|
||||
PlatformDeliveryBatch b = requireBatch(id);
|
||||
requirePendingForHeaderEdit(b);
|
||||
if (request.getPlannedDeliveryDate() == null && request.getRemarks() == null) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"at least one of plannedDeliveryDate or remarks must be provided");
|
||||
}
|
||||
String oldJson = toJson(batchSnapshot(b));
|
||||
if (request.getPlannedDeliveryDate() != null) {
|
||||
b.setPlannedDeliveryDate(parsePlannedDateOrNull(request.getPlannedDeliveryDate()));
|
||||
}
|
||||
if (request.getRemarks() != null) {
|
||||
b.setRemarks(blankToNull(request.getRemarks()));
|
||||
}
|
||||
b.setUpdatedAt(OffsetDateTime.now(ZoneOffset.UTC));
|
||||
batchMapper.updateById(b);
|
||||
auditService.record(
|
||||
AuditEntityTypes.DELIVERY_BATCH,
|
||||
id,
|
||||
AuditActions.DELIVERY_BATCH_UPDATED,
|
||||
null,
|
||||
oldJson,
|
||||
toJson(batchSnapshot(b)));
|
||||
return toResponse(b);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public DeliveryBatchResponse patchStatus(long id, DeliveryBatchStatusPatchRequest request) {
|
||||
PlatformDeliveryBatch b = requireBatch(id);
|
||||
DeliveryBatchStatus from = parseBatchStatus(b.getStatus());
|
||||
DeliveryBatchStatus to = parseBatchStatusOrBadRequest(request.getStatus());
|
||||
if (from == to) {
|
||||
return toResponse(b);
|
||||
}
|
||||
if (from != DeliveryBatchStatus.PENDING) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.CONFLICT, "delivery batch status can only change from PENDING");
|
||||
}
|
||||
if (to != DeliveryBatchStatus.DELIVERED && to != DeliveryBatchStatus.CANCELLED) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST, "only DELIVERED or CANCELLED allowed from PENDING");
|
||||
}
|
||||
String oldJson = toJson(Map.of("status", from.name()));
|
||||
b.setStatus(to.name());
|
||||
if (to == DeliveryBatchStatus.DELIVERED) {
|
||||
b.setFinishedAt(OffsetDateTime.now(ZoneOffset.UTC));
|
||||
} else {
|
||||
b.setFinishedAt(null);
|
||||
}
|
||||
b.setUpdatedAt(OffsetDateTime.now(ZoneOffset.UTC));
|
||||
batchMapper.updateById(b);
|
||||
auditService.record(
|
||||
AuditEntityTypes.DELIVERY_BATCH,
|
||||
id,
|
||||
AuditActions.DELIVERY_BATCH_STATUS_CHANGED,
|
||||
"status",
|
||||
oldJson,
|
||||
toJson(Map.of("status", to.name())));
|
||||
return toResponse(b);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<DeliveryLineResponse> listLines(long batchId) {
|
||||
requireBatch(batchId);
|
||||
return selectLines(batchId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public DeliveryLineResponse addLine(long batchId, DeliveryLineRequest request) {
|
||||
PlatformDeliveryBatch b = requireBatch(batchId);
|
||||
requirePendingForLineMutation(b);
|
||||
validateContractLineForBatch(b, request.getContractLineId());
|
||||
OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC);
|
||||
PlatformDeliveryLine line = new PlatformDeliveryLine();
|
||||
line.setBatchId(batchId);
|
||||
line.setSortOrder(resolveSortOrder(batchId, request.getSortOrder()));
|
||||
line.setDescription(request.getDescription().trim());
|
||||
line.setQuantity(request.getQuantity());
|
||||
line.setContractLineId(request.getContractLineId());
|
||||
line.setCreatedAt(now);
|
||||
line.setUpdatedAt(now);
|
||||
lineMapper.insert(line);
|
||||
auditService.record(
|
||||
AuditEntityTypes.DELIVERY_BATCH,
|
||||
batchId,
|
||||
AuditActions.DELIVERY_LINE_ADDED,
|
||||
null,
|
||||
null,
|
||||
toJson(lineSnapshot(line)));
|
||||
return toLineResponse(line);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public DeliveryLineResponse updateLine(long batchId, long lineId, DeliveryLineRequest request) {
|
||||
PlatformDeliveryBatch b = requireBatch(batchId);
|
||||
requirePendingForLineMutation(b);
|
||||
PlatformDeliveryLine line = requireLine(batchId, lineId);
|
||||
validateContractLineForBatch(b, request.getContractLineId());
|
||||
String oldJson = toJson(lineSnapshot(line));
|
||||
line.setSortOrder(resolveSortOrder(batchId, request.getSortOrder(), line.getSortOrder()));
|
||||
line.setDescription(request.getDescription().trim());
|
||||
line.setQuantity(request.getQuantity());
|
||||
line.setContractLineId(request.getContractLineId());
|
||||
line.setUpdatedAt(OffsetDateTime.now(ZoneOffset.UTC));
|
||||
lineMapper.updateById(line);
|
||||
auditService.record(
|
||||
AuditEntityTypes.DELIVERY_BATCH,
|
||||
batchId,
|
||||
AuditActions.DELIVERY_LINE_UPDATED,
|
||||
"line:" + lineId,
|
||||
oldJson,
|
||||
toJson(lineSnapshot(line)));
|
||||
return toLineResponse(line);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteLine(long batchId, long lineId) {
|
||||
PlatformDeliveryBatch b = requireBatch(batchId);
|
||||
requirePendingForLineMutation(b);
|
||||
PlatformDeliveryLine line = requireLine(batchId, lineId);
|
||||
String oldJson = toJson(lineSnapshot(line));
|
||||
lineMapper.deleteById(lineId);
|
||||
auditService.record(
|
||||
AuditEntityTypes.DELIVERY_BATCH,
|
||||
batchId,
|
||||
AuditActions.DELIVERY_LINE_DELETED,
|
||||
"line:" + lineId,
|
||||
oldJson,
|
||||
null);
|
||||
}
|
||||
|
||||
private List<DeliveryLineResponse> selectLines(long batchId) {
|
||||
LambdaQueryWrapper<PlatformDeliveryLine> q =
|
||||
Wrappers.lambdaQuery(PlatformDeliveryLine.class)
|
||||
.eq(PlatformDeliveryLine::getBatchId, batchId)
|
||||
.orderByAsc(PlatformDeliveryLine::getSortOrder)
|
||||
.orderByAsc(PlatformDeliveryLine::getId);
|
||||
return lineMapper.selectList(q).stream().map(this::toLineResponse).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void requireProject(long projectId) {
|
||||
if (projectMapper.selectById(projectId) == null) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "project not found");
|
||||
}
|
||||
}
|
||||
|
||||
private PlatformContract requireContract(long id) {
|
||||
PlatformContract c = contractMapper.selectById(id);
|
||||
if (c == null) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "contract not found");
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
private PlatformDeliveryBatch requireBatch(long id) {
|
||||
PlatformDeliveryBatch b = batchMapper.selectById(id);
|
||||
if (b == null) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "delivery batch not found");
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
private PlatformDeliveryLine requireLine(long batchId, long lineId) {
|
||||
PlatformDeliveryLine line = lineMapper.selectById(lineId);
|
||||
if (line == null || !line.getBatchId().equals(batchId)) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "delivery line not found");
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
private void requirePendingForHeaderEdit(PlatformDeliveryBatch b) {
|
||||
if (parseBatchStatus(b.getStatus()) != DeliveryBatchStatus.PENDING) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.CONFLICT,
|
||||
"delivery batch can only be updated in PENDING status");
|
||||
}
|
||||
}
|
||||
|
||||
private void requirePendingForLineMutation(PlatformDeliveryBatch b) {
|
||||
requirePendingForHeaderEdit(b);
|
||||
}
|
||||
|
||||
private void validateContractLineForBatch(PlatformDeliveryBatch batch, Long contractLineId) {
|
||||
if (contractLineId == null) {
|
||||
return;
|
||||
}
|
||||
PlatformContractLine cl = contractLineMapper.selectById(contractLineId);
|
||||
if (cl == null) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "contract line not found");
|
||||
}
|
||||
PlatformContract c = requireContract(cl.getContractId());
|
||||
if (!c.getProjectId().equals(batch.getProjectId())) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"contract line must belong to a contract in the same project as the batch");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean existsBatchCode(String batchCode) {
|
||||
return batchMapper.selectCount(
|
||||
Wrappers.lambdaQuery(PlatformDeliveryBatch.class)
|
||||
.eq(PlatformDeliveryBatch::getBatchCode, batchCode))
|
||||
> 0;
|
||||
}
|
||||
|
||||
private static DeliveryBatchStatus parseBatchStatus(String raw) {
|
||||
try {
|
||||
return DeliveryBatchStatus.valueOf(raw);
|
||||
} catch (Exception e) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
"invalid delivery batch status stored: " + raw);
|
||||
}
|
||||
}
|
||||
|
||||
private static DeliveryBatchStatus parseBatchStatusOrBadRequest(String raw) {
|
||||
try {
|
||||
return DeliveryBatchStatus.valueOf(raw.trim());
|
||||
} catch (Exception e) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST, "unknown delivery batch status: " + raw);
|
||||
}
|
||||
}
|
||||
|
||||
private int resolveSortOrder(long batchId, Integer requested) {
|
||||
return resolveSortOrder(batchId, requested, null);
|
||||
}
|
||||
|
||||
private int resolveSortOrder(long batchId, Integer requested, Integer fallbackExisting) {
|
||||
if (requested != null) {
|
||||
return requested;
|
||||
}
|
||||
if (fallbackExisting != null) {
|
||||
return fallbackExisting;
|
||||
}
|
||||
LambdaQueryWrapper<PlatformDeliveryLine> q =
|
||||
Wrappers.lambdaQuery(PlatformDeliveryLine.class)
|
||||
.eq(PlatformDeliveryLine::getBatchId, batchId)
|
||||
.orderByDesc(PlatformDeliveryLine::getSortOrder)
|
||||
.last("LIMIT 1");
|
||||
PlatformDeliveryLine last = lineMapper.selectOne(q);
|
||||
return last == null || last.getSortOrder() == null ? 0 : last.getSortOrder() + 1;
|
||||
}
|
||||
|
||||
private static LocalDate parsePlannedDateOrNull(String raw) {
|
||||
if (!StringUtils.hasText(raw)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return LocalDate.parse(raw.trim());
|
||||
} catch (DateTimeParseException e) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST, "invalid plannedDeliveryDate, expected yyyy-MM-dd");
|
||||
}
|
||||
}
|
||||
|
||||
private static String blankToNull(String s) {
|
||||
return StringUtils.hasText(s) ? s.trim() : null;
|
||||
}
|
||||
|
||||
private Map<String, Object> batchSnapshot(PlatformDeliveryBatch b) {
|
||||
Map<String, Object> m = new LinkedHashMap<>();
|
||||
m.put("id", b.getId());
|
||||
m.put("projectId", b.getProjectId());
|
||||
m.put("contractId", b.getContractId());
|
||||
m.put("batchCode", b.getBatchCode());
|
||||
m.put("plannedDeliveryDate", b.getPlannedDeliveryDate() != null ? b.getPlannedDeliveryDate().toString() : null);
|
||||
m.put("status", b.getStatus());
|
||||
m.put("finishedAt", b.getFinishedAt());
|
||||
m.put("remarks", b.getRemarks());
|
||||
return m;
|
||||
}
|
||||
|
||||
private Map<String, Object> lineSnapshot(PlatformDeliveryLine line) {
|
||||
Map<String, Object> m = new LinkedHashMap<>();
|
||||
m.put("id", line.getId());
|
||||
m.put("batchId", line.getBatchId());
|
||||
m.put("sortOrder", line.getSortOrder());
|
||||
m.put("description", line.getDescription());
|
||||
m.put("quantity", line.getQuantity());
|
||||
m.put("contractLineId", line.getContractLineId());
|
||||
return m;
|
||||
}
|
||||
|
||||
private String toJson(Object value) {
|
||||
try {
|
||||
return objectMapper.writeValueAsString(value);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private DeliveryBatchResponse toResponse(PlatformDeliveryBatch b) {
|
||||
DeliveryBatchResponse r = new DeliveryBatchResponse();
|
||||
r.setId(b.getId());
|
||||
r.setProjectId(b.getProjectId());
|
||||
r.setContractId(b.getContractId());
|
||||
r.setBatchCode(b.getBatchCode());
|
||||
r.setPlannedDeliveryDate(b.getPlannedDeliveryDate());
|
||||
r.setStatus(b.getStatus());
|
||||
r.setFinishedAt(b.getFinishedAt());
|
||||
r.setRemarks(b.getRemarks());
|
||||
r.setCreatedAt(b.getCreatedAt());
|
||||
r.setUpdatedAt(b.getUpdatedAt());
|
||||
return r;
|
||||
}
|
||||
|
||||
private DeliveryLineResponse toLineResponse(PlatformDeliveryLine line) {
|
||||
DeliveryLineResponse r = new DeliveryLineResponse();
|
||||
r.setId(line.getId());
|
||||
r.setBatchId(line.getBatchId());
|
||||
r.setSortOrder(line.getSortOrder());
|
||||
r.setDescription(line.getDescription());
|
||||
r.setQuantity(line.getQuantity());
|
||||
r.setContractLineId(line.getContractLineId());
|
||||
r.setCreatedAt(line.getCreatedAt());
|
||||
r.setUpdatedAt(line.getUpdatedAt());
|
||||
return r;
|
||||
}
|
||||
}
|
||||
+321
@@ -0,0 +1,321 @@
|
||||
package cn.craftlabs.platform.api.service;
|
||||
|
||||
import cn.craftlabs.platform.api.audit.AuditActions;
|
||||
import cn.craftlabs.platform.api.audit.AuditEntityTypes;
|
||||
import cn.craftlabs.platform.api.domain.LicenseSnStatus;
|
||||
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.license.PlatformLicenseSn;
|
||||
import cn.craftlabs.platform.api.persistence.license.PlatformLicenseSnMapper;
|
||||
import cn.craftlabs.platform.api.persistence.project.PlatformProjectMapper;
|
||||
import cn.craftlabs.platform.api.web.dto.LicenseSnCreateRequest;
|
||||
import cn.craftlabs.platform.api.web.dto.LicenseSnResponse;
|
||||
import cn.craftlabs.platform.api.web.dto.LicenseSnStatusPatchRequest;
|
||||
import cn.craftlabs.platform.api.web.dto.LicenseSnUpdateRequest;
|
||||
import cn.craftlabs.platform.api.web.dto.PageResponse;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class LicenseSnService {
|
||||
|
||||
private final PlatformLicenseSnMapper licenseSnMapper;
|
||||
private final PlatformProjectMapper projectMapper;
|
||||
private final PlatformContractLineMapper contractLineMapper;
|
||||
private final PlatformContractMapper contractMapper;
|
||||
private final AuditService auditService;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public LicenseSnService(
|
||||
PlatformLicenseSnMapper licenseSnMapper,
|
||||
PlatformProjectMapper projectMapper,
|
||||
PlatformContractLineMapper contractLineMapper,
|
||||
PlatformContractMapper contractMapper,
|
||||
AuditService auditService,
|
||||
ObjectMapper objectMapper) {
|
||||
this.licenseSnMapper = licenseSnMapper;
|
||||
this.projectMapper = projectMapper;
|
||||
this.contractLineMapper = contractLineMapper;
|
||||
this.contractMapper = contractMapper;
|
||||
this.auditService = auditService;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public LicenseSnResponse create(LicenseSnCreateRequest request) {
|
||||
String code = request.getSnCode().trim();
|
||||
if (!StringUtils.hasText(code)) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "snCode must not be blank");
|
||||
}
|
||||
Long projectId = request.getProjectId();
|
||||
Long contractLineId = request.getContractLineId();
|
||||
if (projectId == null && contractLineId == null) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST, "projectId or contractLineId is required");
|
||||
}
|
||||
if (contractLineId != null) {
|
||||
long derived = projectIdFromContractLine(contractLineId);
|
||||
if (projectId != null && !projectId.equals(derived)) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST, "projectId does not match contract line's project");
|
||||
}
|
||||
projectId = derived;
|
||||
} else {
|
||||
requireProject(projectId);
|
||||
}
|
||||
if (existsSnCode(code)) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "duplicate sn code");
|
||||
}
|
||||
OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC);
|
||||
PlatformLicenseSn row = new PlatformLicenseSn();
|
||||
row.setSnCode(code);
|
||||
row.setProjectId(projectId);
|
||||
row.setContractLineId(contractLineId);
|
||||
row.setStatus(LicenseSnStatus.REGISTERED.name());
|
||||
row.setActivationRemark(blankToNull(request.getActivationRemark()));
|
||||
row.setCreatedAt(now);
|
||||
row.setUpdatedAt(now);
|
||||
licenseSnMapper.insert(row);
|
||||
auditService.record(
|
||||
AuditEntityTypes.LICENSE_SN,
|
||||
row.getId(),
|
||||
AuditActions.LICENSE_SN_CREATED,
|
||||
null,
|
||||
null,
|
||||
toJson(licenseSnapshot(row)));
|
||||
return toResponse(row);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public PageResponse<LicenseSnResponse> page(
|
||||
int page, int size, Long projectId, String keyword, String status) {
|
||||
String kw = StringUtils.hasText(keyword) ? keyword.trim() : null;
|
||||
String st = StringUtils.hasText(status) ? status.trim() : null;
|
||||
if (st != null) {
|
||||
parseLicenseStatusOrBadRequest(st);
|
||||
}
|
||||
LambdaQueryWrapper<PlatformLicenseSn> q =
|
||||
Wrappers.lambdaQuery(PlatformLicenseSn.class)
|
||||
.eq(projectId != null, PlatformLicenseSn::getProjectId, projectId)
|
||||
.like(kw != null, PlatformLicenseSn::getSnCode, kw)
|
||||
.eq(st != null, PlatformLicenseSn::getStatus, st)
|
||||
.orderByDesc(PlatformLicenseSn::getId);
|
||||
Page<PlatformLicenseSn> mpPage = new Page<>(page + 1L, size);
|
||||
licenseSnMapper.selectPage(mpPage, q);
|
||||
List<LicenseSnResponse> content =
|
||||
mpPage.getRecords().stream().map(this::toResponse).collect(Collectors.toList());
|
||||
return new PageResponse<>(content, mpPage.getTotal(), page, size);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public LicenseSnResponse getById(long id) {
|
||||
return toResponse(requireLicense(id));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public LicenseSnResponse update(long id, LicenseSnUpdateRequest request) {
|
||||
PlatformLicenseSn row = requireLicense(id);
|
||||
if (parseLicenseStatus(row.getStatus()) == LicenseSnStatus.REVOKED) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.CONFLICT, "revoked license SN cannot be updated");
|
||||
}
|
||||
if (request.getProjectId() == null
|
||||
&& request.getContractLineId() == null
|
||||
&& request.getActivationRemark() == null) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"at least one of projectId, contractLineId, or activationRemark must be provided");
|
||||
}
|
||||
String oldJson = toJson(licenseSnapshot(row));
|
||||
if (request.getContractLineId() != null) {
|
||||
long derivedProject = projectIdFromContractLine(request.getContractLineId());
|
||||
row.setContractLineId(request.getContractLineId());
|
||||
row.setProjectId(derivedProject);
|
||||
if (request.getProjectId() != null && !request.getProjectId().equals(derivedProject)) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST, "projectId does not match contract line's project");
|
||||
}
|
||||
} else if (request.getProjectId() != null) {
|
||||
requireProject(request.getProjectId());
|
||||
if (row.getContractLineId() != null) {
|
||||
long lineProject = projectIdFromContractLine(row.getContractLineId());
|
||||
if (!request.getProjectId().equals(lineProject)) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"projectId does not match existing contract line binding");
|
||||
}
|
||||
}
|
||||
row.setProjectId(request.getProjectId());
|
||||
}
|
||||
if (request.getActivationRemark() != null) {
|
||||
row.setActivationRemark(blankToNull(request.getActivationRemark()));
|
||||
}
|
||||
if (row.getProjectId() == null && row.getContractLineId() == null) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST, "projectId or contractLineId must remain set");
|
||||
}
|
||||
row.setUpdatedAt(OffsetDateTime.now(ZoneOffset.UTC));
|
||||
licenseSnMapper.updateById(row);
|
||||
auditService.record(
|
||||
AuditEntityTypes.LICENSE_SN,
|
||||
id,
|
||||
AuditActions.LICENSE_SN_UPDATED,
|
||||
null,
|
||||
oldJson,
|
||||
toJson(licenseSnapshot(row)));
|
||||
return toResponse(row);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public LicenseSnResponse patchStatus(long id, LicenseSnStatusPatchRequest request) {
|
||||
PlatformLicenseSn row = requireLicense(id);
|
||||
LicenseSnStatus from = parseLicenseStatus(row.getStatus());
|
||||
LicenseSnStatus to = parseLicenseStatusOrBadRequest(request.getStatus());
|
||||
if (from == to) {
|
||||
return toResponse(row);
|
||||
}
|
||||
if (from == LicenseSnStatus.REVOKED) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.CONFLICT, "revoked license SN status cannot be changed");
|
||||
}
|
||||
if (!allowedLicenseTransition(from, to)) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.CONFLICT, "illegal license SN status transition");
|
||||
}
|
||||
String oldJson = toJson(Map.of("status", from.name()));
|
||||
row.setStatus(to.name());
|
||||
row.setUpdatedAt(OffsetDateTime.now(ZoneOffset.UTC));
|
||||
licenseSnMapper.updateById(row);
|
||||
auditService.record(
|
||||
AuditEntityTypes.LICENSE_SN,
|
||||
id,
|
||||
AuditActions.LICENSE_SN_STATUS_CHANGED,
|
||||
"status",
|
||||
oldJson,
|
||||
toJson(Map.of("status", to.name())));
|
||||
return toResponse(row);
|
||||
}
|
||||
|
||||
private static boolean allowedLicenseTransition(LicenseSnStatus from, LicenseSnStatus to) {
|
||||
if (to == LicenseSnStatus.REVOKED) {
|
||||
return true;
|
||||
}
|
||||
if (from == LicenseSnStatus.REGISTERED) {
|
||||
return to == LicenseSnStatus.ISSUED;
|
||||
}
|
||||
if (from == LicenseSnStatus.ISSUED) {
|
||||
return to == LicenseSnStatus.ACTIVATED;
|
||||
}
|
||||
if (from == LicenseSnStatus.ACTIVATED) {
|
||||
return to == LicenseSnStatus.SUSPENDED;
|
||||
}
|
||||
if (from == LicenseSnStatus.SUSPENDED) {
|
||||
return to == LicenseSnStatus.ACTIVATED;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private long projectIdFromContractLine(long contractLineId) {
|
||||
PlatformContractLine line = contractLineMapper.selectById(contractLineId);
|
||||
if (line == null) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "contract line not found");
|
||||
}
|
||||
PlatformContract c = contractMapper.selectById(line.getContractId());
|
||||
if (c == null) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.INTERNAL_SERVER_ERROR, "contract missing for contract line");
|
||||
}
|
||||
return c.getProjectId();
|
||||
}
|
||||
|
||||
private void requireProject(long projectId) {
|
||||
if (projectMapper.selectById(projectId) == null) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "project not found");
|
||||
}
|
||||
}
|
||||
|
||||
private PlatformLicenseSn requireLicense(long id) {
|
||||
PlatformLicenseSn row = licenseSnMapper.selectById(id);
|
||||
if (row == null) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "license SN not found");
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
private boolean existsSnCode(String snCode) {
|
||||
return licenseSnMapper.selectCount(
|
||||
Wrappers.lambdaQuery(PlatformLicenseSn.class)
|
||||
.eq(PlatformLicenseSn::getSnCode, snCode))
|
||||
> 0;
|
||||
}
|
||||
|
||||
private static LicenseSnStatus parseLicenseStatus(String raw) {
|
||||
try {
|
||||
return LicenseSnStatus.valueOf(raw);
|
||||
} catch (Exception e) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.INTERNAL_SERVER_ERROR, "invalid license SN status stored: " + raw);
|
||||
}
|
||||
}
|
||||
|
||||
private static LicenseSnStatus parseLicenseStatusOrBadRequest(String raw) {
|
||||
try {
|
||||
return LicenseSnStatus.valueOf(raw.trim());
|
||||
} catch (Exception e) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "unknown license SN status: " + raw);
|
||||
}
|
||||
}
|
||||
|
||||
private static String blankToNull(String s) {
|
||||
return StringUtils.hasText(s) ? s.trim() : null;
|
||||
}
|
||||
|
||||
private Map<String, Object> licenseSnapshot(PlatformLicenseSn row) {
|
||||
Map<String, Object> m = new LinkedHashMap<>();
|
||||
m.put("id", row.getId());
|
||||
m.put("snCode", row.getSnCode());
|
||||
m.put("projectId", row.getProjectId());
|
||||
m.put("contractLineId", row.getContractLineId());
|
||||
m.put("status", row.getStatus());
|
||||
m.put("activationRemark", row.getActivationRemark());
|
||||
return m;
|
||||
}
|
||||
|
||||
private String toJson(Object value) {
|
||||
try {
|
||||
return objectMapper.writeValueAsString(value);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private LicenseSnResponse toResponse(PlatformLicenseSn row) {
|
||||
LicenseSnResponse r = new LicenseSnResponse();
|
||||
r.setId(row.getId());
|
||||
r.setSnCode(row.getSnCode());
|
||||
r.setProjectId(row.getProjectId());
|
||||
r.setContractLineId(row.getContractLineId());
|
||||
r.setStatus(row.getStatus());
|
||||
r.setActivationRemark(row.getActivationRemark());
|
||||
r.setCreatedAt(row.getCreatedAt());
|
||||
r.setUpdatedAt(row.getUpdatedAt());
|
||||
return r;
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package cn.craftlabs.platform.api.web.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
public class DeliveryBatchCreateRequest {
|
||||
|
||||
@NotNull private Long projectId;
|
||||
|
||||
private Long contractId;
|
||||
|
||||
@NotBlank
|
||||
@Size(max = 64)
|
||||
private String batchCode;
|
||||
|
||||
/** ISO-8601 date string {@code yyyy-MM-dd},可选 */
|
||||
private String plannedDeliveryDate;
|
||||
|
||||
@Size(max = 4000)
|
||||
private String remarks;
|
||||
|
||||
public Long getProjectId() {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
public void setProjectId(Long projectId) {
|
||||
this.projectId = projectId;
|
||||
}
|
||||
|
||||
public Long getContractId() {
|
||||
return contractId;
|
||||
}
|
||||
|
||||
public void setContractId(Long contractId) {
|
||||
this.contractId = contractId;
|
||||
}
|
||||
|
||||
public String getBatchCode() {
|
||||
return batchCode;
|
||||
}
|
||||
|
||||
public void setBatchCode(String batchCode) {
|
||||
this.batchCode = batchCode;
|
||||
}
|
||||
|
||||
public String getPlannedDeliveryDate() {
|
||||
return plannedDeliveryDate;
|
||||
}
|
||||
|
||||
public void setPlannedDeliveryDate(String plannedDeliveryDate) {
|
||||
this.plannedDeliveryDate = plannedDeliveryDate;
|
||||
}
|
||||
|
||||
public String getRemarks() {
|
||||
return remarks;
|
||||
}
|
||||
|
||||
public void setRemarks(String remarks) {
|
||||
this.remarks = remarks;
|
||||
}
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
package cn.craftlabs.platform.api.web.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.List;
|
||||
|
||||
public class DeliveryBatchResponse {
|
||||
|
||||
private Long id;
|
||||
private Long projectId;
|
||||
private Long contractId;
|
||||
private String batchCode;
|
||||
private LocalDate plannedDeliveryDate;
|
||||
private String status;
|
||||
private OffsetDateTime finishedAt;
|
||||
private String remarks;
|
||||
private OffsetDateTime createdAt;
|
||||
private OffsetDateTime updatedAt;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private List<DeliveryLineResponse> lines;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Long getProjectId() {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
public void setProjectId(Long projectId) {
|
||||
this.projectId = projectId;
|
||||
}
|
||||
|
||||
public Long getContractId() {
|
||||
return contractId;
|
||||
}
|
||||
|
||||
public void setContractId(Long contractId) {
|
||||
this.contractId = contractId;
|
||||
}
|
||||
|
||||
public String getBatchCode() {
|
||||
return batchCode;
|
||||
}
|
||||
|
||||
public void setBatchCode(String batchCode) {
|
||||
this.batchCode = batchCode;
|
||||
}
|
||||
|
||||
public LocalDate getPlannedDeliveryDate() {
|
||||
return plannedDeliveryDate;
|
||||
}
|
||||
|
||||
public void setPlannedDeliveryDate(LocalDate plannedDeliveryDate) {
|
||||
this.plannedDeliveryDate = plannedDeliveryDate;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public OffsetDateTime getFinishedAt() {
|
||||
return finishedAt;
|
||||
}
|
||||
|
||||
public void setFinishedAt(OffsetDateTime finishedAt) {
|
||||
this.finishedAt = finishedAt;
|
||||
}
|
||||
|
||||
public String getRemarks() {
|
||||
return remarks;
|
||||
}
|
||||
|
||||
public void setRemarks(String remarks) {
|
||||
this.remarks = remarks;
|
||||
}
|
||||
|
||||
public OffsetDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public OffsetDateTime getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(OffsetDateTime updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
public List<DeliveryLineResponse> getLines() {
|
||||
return lines;
|
||||
}
|
||||
|
||||
public void setLines(List<DeliveryLineResponse> lines) {
|
||||
this.lines = lines;
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package cn.craftlabs.platform.api.web.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
public class DeliveryBatchStatusPatchRequest {
|
||||
|
||||
@NotBlank
|
||||
private String status;
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package cn.craftlabs.platform.api.web.dto;
|
||||
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
public class DeliveryBatchUpdateRequest {
|
||||
|
||||
private String plannedDeliveryDate;
|
||||
|
||||
@Size(max = 4000)
|
||||
private String remarks;
|
||||
|
||||
public String getPlannedDeliveryDate() {
|
||||
return plannedDeliveryDate;
|
||||
}
|
||||
|
||||
public void setPlannedDeliveryDate(String plannedDeliveryDate) {
|
||||
this.plannedDeliveryDate = plannedDeliveryDate;
|
||||
}
|
||||
|
||||
public String getRemarks() {
|
||||
return remarks;
|
||||
}
|
||||
|
||||
public void setRemarks(String remarks) {
|
||||
this.remarks = remarks;
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package cn.craftlabs.platform.api.web.dto;
|
||||
|
||||
import jakarta.validation.constraints.DecimalMin;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class DeliveryLineRequest {
|
||||
|
||||
private Integer sortOrder;
|
||||
|
||||
@NotBlank
|
||||
@Size(max = 512)
|
||||
private String description;
|
||||
|
||||
@NotNull
|
||||
@DecimalMin(value = "0.0001", inclusive = true)
|
||||
private BigDecimal quantity;
|
||||
|
||||
private Long contractLineId;
|
||||
|
||||
public Integer getSortOrder() {
|
||||
return sortOrder;
|
||||
}
|
||||
|
||||
public void setSortOrder(Integer sortOrder) {
|
||||
this.sortOrder = sortOrder;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public BigDecimal getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public void setQuantity(BigDecimal quantity) {
|
||||
this.quantity = quantity;
|
||||
}
|
||||
|
||||
public Long getContractLineId() {
|
||||
return contractLineId;
|
||||
}
|
||||
|
||||
public void setContractLineId(Long contractLineId) {
|
||||
this.contractLineId = contractLineId;
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package cn.craftlabs.platform.api.web.dto;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
public class DeliveryLineResponse {
|
||||
|
||||
private Long id;
|
||||
private Long batchId;
|
||||
private Integer sortOrder;
|
||||
private String description;
|
||||
private BigDecimal quantity;
|
||||
private Long contractLineId;
|
||||
private OffsetDateTime createdAt;
|
||||
private OffsetDateTime updatedAt;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Long getBatchId() {
|
||||
return batchId;
|
||||
}
|
||||
|
||||
public void setBatchId(Long batchId) {
|
||||
this.batchId = batchId;
|
||||
}
|
||||
|
||||
public Integer getSortOrder() {
|
||||
return sortOrder;
|
||||
}
|
||||
|
||||
public void setSortOrder(Integer sortOrder) {
|
||||
this.sortOrder = sortOrder;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public BigDecimal getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public void setQuantity(BigDecimal quantity) {
|
||||
this.quantity = quantity;
|
||||
}
|
||||
|
||||
public Long getContractLineId() {
|
||||
return contractLineId;
|
||||
}
|
||||
|
||||
public void setContractLineId(Long contractLineId) {
|
||||
this.contractLineId = contractLineId;
|
||||
}
|
||||
|
||||
public OffsetDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public OffsetDateTime getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(OffsetDateTime updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package cn.craftlabs.platform.api.web.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
public class LicenseSnCreateRequest {
|
||||
|
||||
@NotBlank
|
||||
@Size(max = 128)
|
||||
private String snCode;
|
||||
|
||||
private Long projectId;
|
||||
private Long contractLineId;
|
||||
|
||||
@Size(max = 512)
|
||||
private String activationRemark;
|
||||
|
||||
public String getSnCode() {
|
||||
return snCode;
|
||||
}
|
||||
|
||||
public void setSnCode(String snCode) {
|
||||
this.snCode = snCode;
|
||||
}
|
||||
|
||||
public Long getProjectId() {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
public void setProjectId(Long projectId) {
|
||||
this.projectId = projectId;
|
||||
}
|
||||
|
||||
public Long getContractLineId() {
|
||||
return contractLineId;
|
||||
}
|
||||
|
||||
public void setContractLineId(Long contractLineId) {
|
||||
this.contractLineId = contractLineId;
|
||||
}
|
||||
|
||||
public String getActivationRemark() {
|
||||
return activationRemark;
|
||||
}
|
||||
|
||||
public void setActivationRemark(String activationRemark) {
|
||||
this.activationRemark = activationRemark;
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package cn.craftlabs.platform.api.web.dto;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
public class LicenseSnResponse {
|
||||
|
||||
private Long id;
|
||||
private String snCode;
|
||||
private Long projectId;
|
||||
private Long contractLineId;
|
||||
private String status;
|
||||
private String activationRemark;
|
||||
private OffsetDateTime createdAt;
|
||||
private OffsetDateTime updatedAt;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getSnCode() {
|
||||
return snCode;
|
||||
}
|
||||
|
||||
public void setSnCode(String snCode) {
|
||||
this.snCode = snCode;
|
||||
}
|
||||
|
||||
public Long getProjectId() {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
public void setProjectId(Long projectId) {
|
||||
this.projectId = projectId;
|
||||
}
|
||||
|
||||
public Long getContractLineId() {
|
||||
return contractLineId;
|
||||
}
|
||||
|
||||
public void setContractLineId(Long contractLineId) {
|
||||
this.contractLineId = contractLineId;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getActivationRemark() {
|
||||
return activationRemark;
|
||||
}
|
||||
|
||||
public void setActivationRemark(String activationRemark) {
|
||||
this.activationRemark = activationRemark;
|
||||
}
|
||||
|
||||
public OffsetDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public OffsetDateTime getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(OffsetDateTime updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package cn.craftlabs.platform.api.web.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
public class LicenseSnStatusPatchRequest {
|
||||
|
||||
@NotBlank
|
||||
private String status;
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package cn.craftlabs.platform.api.web.dto;
|
||||
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
/** 更新绑定关系或激活备注(非 TERMINAL 状态下允许调整,具体见服务校验)。 */
|
||||
public class LicenseSnUpdateRequest {
|
||||
|
||||
private Long projectId;
|
||||
private Long contractLineId;
|
||||
|
||||
@Size(max = 512)
|
||||
private String activationRemark;
|
||||
|
||||
public Long getProjectId() {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
public void setProjectId(Long projectId) {
|
||||
this.projectId = projectId;
|
||||
}
|
||||
|
||||
public Long getContractLineId() {
|
||||
return contractLineId;
|
||||
}
|
||||
|
||||
public void setContractLineId(Long contractLineId) {
|
||||
this.contractLineId = contractLineId;
|
||||
}
|
||||
|
||||
public String getActivationRemark() {
|
||||
return activationRemark;
|
||||
}
|
||||
|
||||
public void setActivationRemark(String activationRemark) {
|
||||
this.activationRemark = activationRemark;
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
-- I4:M3 交付批次与清单;M4 许可 SN 台账(PostgreSQL 15;H2 MODE=PostgreSQL)
|
||||
CREATE TABLE platform_delivery_batch (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
project_id BIGINT NOT NULL REFERENCES platform_project (id),
|
||||
contract_id BIGINT REFERENCES platform_contract (id),
|
||||
batch_code VARCHAR(64) NOT NULL,
|
||||
planned_delivery_date DATE,
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
|
||||
finished_at TIMESTAMP WITH TIME ZONE,
|
||||
remarks TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT uq_platform_delivery_batch_code UNIQUE (batch_code)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_platform_delivery_batch_project ON platform_delivery_batch (project_id);
|
||||
CREATE INDEX idx_platform_delivery_batch_contract ON platform_delivery_batch (contract_id);
|
||||
|
||||
CREATE TABLE platform_delivery_line (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
batch_id BIGINT NOT NULL REFERENCES platform_delivery_batch (id) ON DELETE CASCADE,
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
description VARCHAR(512) NOT NULL,
|
||||
quantity NUMERIC(18, 4) NOT NULL DEFAULT 1,
|
||||
contract_line_id BIGINT REFERENCES platform_contract_line (id),
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_platform_delivery_line_batch ON platform_delivery_line (batch_id);
|
||||
|
||||
CREATE TABLE platform_license_sn (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
sn_code VARCHAR(128) NOT NULL,
|
||||
project_id BIGINT REFERENCES platform_project (id),
|
||||
contract_line_id BIGINT REFERENCES platform_contract_line (id),
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'REGISTERED',
|
||||
activation_remark VARCHAR(512),
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT uq_platform_license_sn_code UNIQUE (sn_code)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_platform_license_sn_project ON platform_license_sn (project_id);
|
||||
CREATE INDEX idx_platform_license_sn_contract_line ON platform_license_sn (contract_line_id);
|
||||
+250
@@ -0,0 +1,250 @@
|
||||
package cn.craftlabs.platform.api.delivery;
|
||||
|
||||
import cn.craftlabs.platform.api.support.JwtTestSupport;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
@Transactional
|
||||
class DeliveryBatchControllerTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Test
|
||||
void deliveryBatchLinesStatusAuditAndGuards() throws Exception {
|
||||
String token = JwtTestSupport.obtainBearerToken(mockMvc, objectMapper);
|
||||
String auth = "Bearer " + token;
|
||||
|
||||
String customerBody = "{\"name\":\"交付客户\",\"creditCode\":\"DB001\",\"status\":\"ACTIVE\"}";
|
||||
String customerJson =
|
||||
mockMvc.perform(
|
||||
post("/api/v1/customers")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(customerBody))
|
||||
.andExpect(status().isCreated())
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
long customerId = objectMapper.readTree(customerJson).get("id").asLong();
|
||||
|
||||
String projectBody =
|
||||
String.format(
|
||||
"{\"customerId\":%d,\"name\":\"交付项目\",\"phase\":\"PLANNING\"}", customerId);
|
||||
String projectJson =
|
||||
mockMvc.perform(
|
||||
post("/api/v1/projects")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(projectBody))
|
||||
.andExpect(status().isCreated())
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
long projectId = objectMapper.readTree(projectJson).get("id").asLong();
|
||||
|
||||
String otherProjectBody =
|
||||
String.format(
|
||||
"{\"customerId\":%d,\"name\":\"其他项目\",\"phase\":\"PLANNING\"}", customerId);
|
||||
String otherProjectJson =
|
||||
mockMvc.perform(
|
||||
post("/api/v1/projects")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(otherProjectBody))
|
||||
.andExpect(status().isCreated())
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
long otherProjectId = objectMapper.readTree(otherProjectJson).get("id").asLong();
|
||||
|
||||
String contractBody =
|
||||
String.format(
|
||||
"{\"customerId\":%d,\"projectId\":%d,\"title\":\"交付合同\",\"remarks\":\"\"}",
|
||||
customerId, projectId);
|
||||
String contractJson =
|
||||
mockMvc.perform(
|
||||
post("/api/v1/contracts")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(contractBody))
|
||||
.andExpect(status().isCreated())
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
long contractId = objectMapper.readTree(contractJson).get("id").asLong();
|
||||
|
||||
String lineBody = "{\"itemName\":\"合同行A\",\"quantity\":1,\"unit\":\"套\",\"amount\":1,\"remark\":\"\"}";
|
||||
String lineJson =
|
||||
mockMvc.perform(
|
||||
post("/api/v1/contracts/" + contractId + "/lines")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(lineBody))
|
||||
.andExpect(status().isCreated())
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
long contractLineId = objectMapper.readTree(lineJson).get("id").asLong();
|
||||
|
||||
String wrongContractBody =
|
||||
String.format(
|
||||
"{\"customerId\":%d,\"projectId\":%d,\"title\":\"错项目合同\",\"remarks\":\"\"}",
|
||||
customerId, otherProjectId);
|
||||
String wrongContractJson =
|
||||
mockMvc.perform(
|
||||
post("/api/v1/contracts")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(wrongContractBody))
|
||||
.andExpect(status().isCreated())
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
long wrongContractId = objectMapper.readTree(wrongContractJson).get("id").asLong();
|
||||
|
||||
String batchMismatch =
|
||||
String.format(
|
||||
"{\"projectId\":%d,\"contractId\":%d,\"batchCode\":\"B-MISMATCH\","
|
||||
+ "\"plannedDeliveryDate\":\"2026-05-01\",\"remarks\":\"x\"}",
|
||||
projectId, wrongContractId);
|
||||
mockMvc.perform(
|
||||
post("/api/v1/delivery-batches")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(batchMismatch))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.message").value(containsString("projectId")));
|
||||
|
||||
String batchCreate =
|
||||
String.format(
|
||||
"{\"projectId\":%d,\"contractId\":%d,\"batchCode\":\"B-001\","
|
||||
+ "\"plannedDeliveryDate\":\"2026-04-01\",\"remarks\":\"首批\"}",
|
||||
projectId, contractId);
|
||||
String batchJson =
|
||||
mockMvc.perform(
|
||||
post("/api/v1/delivery-batches")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(batchCreate))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.status").value("PENDING"))
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
long batchId = objectMapper.readTree(batchJson).get("id").asLong();
|
||||
|
||||
mockMvc.perform(
|
||||
post("/api/v1/delivery-batches")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(batchCreate))
|
||||
.andExpect(status().isConflict())
|
||||
.andExpect(jsonPath("$.message").value(containsString("duplicate")));
|
||||
|
||||
mockMvc.perform(
|
||||
get("/api/v1/delivery-batches")
|
||||
.header("Authorization", auth)
|
||||
.param("page", "0")
|
||||
.param("size", "10")
|
||||
.param("keyword", "B-0"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content[0].id").value(batchId))
|
||||
.andExpect(jsonPath("$.content[0].lines").doesNotExist());
|
||||
|
||||
mockMvc.perform(get("/api/v1/delivery-batches/" + batchId).header("Authorization", auth))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.lines").isArray());
|
||||
|
||||
String dLine =
|
||||
String.format(
|
||||
"{\"description\":\"清单项\",\"quantity\":2,\"contractLineId\":%d}",
|
||||
contractLineId);
|
||||
mockMvc.perform(
|
||||
post("/api/v1/delivery-batches/" + batchId + "/lines")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(dLine))
|
||||
.andExpect(status().isCreated());
|
||||
|
||||
mockMvc.perform(
|
||||
put("/api/v1/delivery-batches/" + batchId)
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"remarks\":\"改备注\"}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.remarks").value("改备注"));
|
||||
|
||||
mockMvc.perform(
|
||||
patch("/api/v1/delivery-batches/" + batchId + "/status")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"status\":\"DELIVERED\"}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.status").value("DELIVERED"))
|
||||
.andExpect(jsonPath("$.finishedAt").exists());
|
||||
|
||||
mockMvc.perform(
|
||||
put("/api/v1/delivery-batches/" + batchId)
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"remarks\":\"迟了\"}"))
|
||||
.andExpect(status().isConflict());
|
||||
|
||||
mockMvc.perform(
|
||||
post("/api/v1/delivery-batches/" + batchId + "/lines")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"description\":\"晚加\",\"quantity\":1}"))
|
||||
.andExpect(status().isConflict());
|
||||
|
||||
String auditBody =
|
||||
mockMvc.perform(
|
||||
get("/api/v1/audit-events")
|
||||
.header("Authorization", auth)
|
||||
.param("entityType", "DELIVERY_BATCH")
|
||||
.param("entityId", String.valueOf(batchId))
|
||||
.param("page", "0")
|
||||
.param("size", "50"))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
|
||||
JsonNode root = objectMapper.readTree(auditBody);
|
||||
boolean hasCreated = false;
|
||||
boolean hasLine = false;
|
||||
for (JsonNode row : root.get("content")) {
|
||||
String action = row.get("action").asText();
|
||||
if ("DELIVERY_BATCH_CREATED".equals(action)) {
|
||||
hasCreated = true;
|
||||
}
|
||||
if ("DELIVERY_LINE_ADDED".equals(action)) {
|
||||
hasLine = true;
|
||||
}
|
||||
}
|
||||
assertThat(hasCreated).isTrue();
|
||||
assertThat(hasLine).isTrue();
|
||||
}
|
||||
}
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
package cn.craftlabs.platform.api.license;
|
||||
|
||||
import cn.craftlabs.platform.api.support.JwtTestSupport;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
@Transactional
|
||||
class LicenseSnControllerTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Test
|
||||
void licenseSnCreateDuplicateStatusTransitionsAndRevoke() throws Exception {
|
||||
String token = JwtTestSupport.obtainBearerToken(mockMvc, objectMapper);
|
||||
String auth = "Bearer " + token;
|
||||
|
||||
String customerBody = "{\"name\":\"SN客户\",\"creditCode\":\"SN001\",\"status\":\"ACTIVE\"}";
|
||||
String customerJson =
|
||||
mockMvc.perform(
|
||||
post("/api/v1/customers")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(customerBody))
|
||||
.andExpect(status().isCreated())
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
long customerId = objectMapper.readTree(customerJson).get("id").asLong();
|
||||
|
||||
String projectBody =
|
||||
String.format(
|
||||
"{\"customerId\":%d,\"name\":\"SN项目\",\"phase\":\"PLANNING\"}", customerId);
|
||||
String projectJson =
|
||||
mockMvc.perform(
|
||||
post("/api/v1/projects")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(projectBody))
|
||||
.andExpect(status().isCreated())
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
long projectId = objectMapper.readTree(projectJson).get("id").asLong();
|
||||
|
||||
String contractBody =
|
||||
String.format(
|
||||
"{\"customerId\":%d,\"projectId\":%d,\"title\":\"SN合同\",\"remarks\":\"\"}",
|
||||
customerId, projectId);
|
||||
String contractJson =
|
||||
mockMvc.perform(
|
||||
post("/api/v1/contracts")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(contractBody))
|
||||
.andExpect(status().isCreated())
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
long contractId = objectMapper.readTree(contractJson).get("id").asLong();
|
||||
|
||||
String lineBody = "{\"itemName\":\"许可行\",\"quantity\":1,\"unit\":\"个\",\"amount\":1,\"remark\":\"\"}";
|
||||
String lineJson =
|
||||
mockMvc.perform(
|
||||
post("/api/v1/contracts/" + contractId + "/lines")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(lineBody))
|
||||
.andExpect(status().isCreated())
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
long contractLineId = objectMapper.readTree(lineJson).get("id").asLong();
|
||||
|
||||
mockMvc.perform(
|
||||
post("/api/v1/license-sns")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"snCode\":\" SN-001 \",\"activationRemark\":\"a\"}"))
|
||||
.andExpect(status().isBadRequest());
|
||||
|
||||
String createByProject =
|
||||
String.format(
|
||||
"{\"snCode\":\"SN-001\",\"projectId\":%d,\"activationRemark\":\"备注\"}",
|
||||
projectId);
|
||||
String snJson =
|
||||
mockMvc.perform(
|
||||
post("/api/v1/license-sns")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(createByProject))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.status").value("REGISTERED"))
|
||||
.andExpect(jsonPath("$.snCode").value("SN-001"))
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
long snId = objectMapper.readTree(snJson).get("id").asLong();
|
||||
|
||||
mockMvc.perform(
|
||||
post("/api/v1/license-sns")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(createByProject))
|
||||
.andExpect(status().isConflict())
|
||||
.andExpect(jsonPath("$.message").value(containsString("duplicate")));
|
||||
|
||||
String createByLineOnly =
|
||||
String.format("{\"snCode\":\"SN-LINE\",\"contractLineId\":%d}", contractLineId);
|
||||
mockMvc.perform(
|
||||
post("/api/v1/license-sns")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(createByLineOnly))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.projectId").value(projectId))
|
||||
.andExpect(jsonPath("$.contractLineId").value(contractLineId));
|
||||
|
||||
mockMvc.perform(
|
||||
get("/api/v1/license-sns")
|
||||
.header("Authorization", auth)
|
||||
.param("page", "0")
|
||||
.param("size", "20")
|
||||
.param("projectId", String.valueOf(projectId))
|
||||
.param("keyword", "SN-")
|
||||
.param("status", "REGISTERED"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content.length()").value(2));
|
||||
|
||||
mockMvc.perform(
|
||||
patch("/api/v1/license-sns/" + snId + "/status")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"status\":\"ACTIVATED\"}"))
|
||||
.andExpect(status().isConflict())
|
||||
.andExpect(jsonPath("$.message").value(containsString("illegal")));
|
||||
|
||||
mockMvc.perform(
|
||||
patch("/api/v1/license-sns/" + snId + "/status")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"status\":\"ISSUED\"}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.status").value("ISSUED"));
|
||||
|
||||
mockMvc.perform(
|
||||
patch("/api/v1/license-sns/" + snId + "/status")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"status\":\"ACTIVATED\"}"))
|
||||
.andExpect(status().isOk());
|
||||
|
||||
mockMvc.perform(
|
||||
put("/api/v1/license-sns/" + snId)
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"activationRemark\":\"已激活\"}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.activationRemark").value("已激活"));
|
||||
|
||||
mockMvc.perform(
|
||||
patch("/api/v1/license-sns/" + snId + "/status")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"status\":\"REVOKED\"}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.status").value("REVOKED"));
|
||||
|
||||
mockMvc.perform(
|
||||
put("/api/v1/license-sns/" + snId)
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"activationRemark\":\"no\"}"))
|
||||
.andExpect(status().isConflict());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user