This commit is contained in:
2026-08-08 12:01:45 +08:00
parent 1a2f598c9a
commit a35cd868b9
56 changed files with 7178 additions and 735 deletions
@@ -56,6 +56,21 @@ public class InternalMeasurementImageController {
images.uploadMeasurementEvidence(tenantId, inboundId, operatorId, file));
}
@PostMapping(value = "/receipts/{tenantId}/{receiptId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ApiResponse<MeasurementImageService.UploadResult> uploadReceipt(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@RequestHeader("X-Operator-Id") long operatorId,
@PathVariable long tenantId,
@PathVariable long receiptId,
@RequestPart("file") MultipartFile file) {
requireToken(token);
if (tenantId <= 0 || receiptId <= 0 || operatorId <= 0) {
throw new BizException(ResultCode.BAD_REQUEST, "客户、收件记录和操作主体ID必须大于0");
}
return ApiResponse.success(images.uploadMojiaReceiptEvidence(
tenantId, receiptId, operatorId, file));
}
@DeleteMapping("/{tenantId}/{inboundId}/{fileId}")
public ApiResponse<Void> delete(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@@ -71,6 +86,21 @@ public class InternalMeasurementImageController {
return ApiResponse.success(null);
}
@DeleteMapping("/receipts/{tenantId}/{receiptId}/{fileId}")
public ApiResponse<Void> deleteReceipt(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@RequestHeader("X-Operator-Id") long operatorId,
@PathVariable long tenantId,
@PathVariable long receiptId,
@PathVariable long fileId) {
requireToken(token);
if (tenantId <= 0 || receiptId <= 0 || fileId <= 0 || operatorId <= 0) {
throw new BizException(ResultCode.BAD_REQUEST, "客户、收件记录、文件和操作主体ID必须大于0");
}
images.deleteMojiaReceiptEvidence(tenantId, receiptId, fileId, operatorId);
return ApiResponse.success(null);
}
@GetMapping("/{tenantId}/{inboundId}/{fileId}")
public ResponseEntity<byte[]> download(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@@ -51,6 +51,16 @@ public class MeasurementImageService {
object.getSizeBytes(), "NOT_REQUIRED", null, null, null, null, null);
}
/** 保存尚未匹配正式入库单的墨家联调图片。 */
@Transactional
public UploadResult uploadMojiaReceiptEvidence(long tenantId, long receiptId, long operatorId,
MultipartFile file) {
FileObjectEntity object = storeObject(
tenantId, receiptId, operatorId, file, "MOJIA_MEASUREMENT_RECEIPT");
return new UploadResult(object.getId(), object.getOriginalName(), object.getContentType(),
object.getSizeBytes(), "NOT_REQUIRED", null, null, null, null, null);
}
private FileObjectEntity storeObject(long tenantId, long inboundId, long operatorId,
MultipartFile file, String businessType) {
var stored = storage.store(tenantId, inboundId, file);
@@ -124,11 +134,19 @@ public class MeasurementImageService {
@Transactional
public void deleteMeasurementEvidence(long tenantId, long inboundId, long fileId, long operatorId) {
FileObjectEntity object = requireObject(tenantId, inboundId, fileId);
if (!Set.of("INBOUND_WEIGHT_IMAGE", "INBOUND_MEASUREMENT_IMAGE")
.contains(object.getBusinessType())) {
throw new BizException(ResultCode.CONFLICT, "仅允许清理入库测量图片");
}
FileObjectEntity object = requireObject(tenantId, inboundId, fileId,
Set.of("INBOUND_WEIGHT_IMAGE", "INBOUND_MEASUREMENT_IMAGE"));
deleteObject(object, operatorId);
}
@Transactional
public void deleteMojiaReceiptEvidence(long tenantId, long receiptId, long fileId, long operatorId) {
FileObjectEntity object = requireObject(tenantId, receiptId, fileId,
Set.of("MOJIA_MEASUREMENT_RECEIPT"));
deleteObject(object, operatorId);
}
private void deleteObject(FileObjectEntity object, long operatorId) {
object.setStatus("DELETED");
object.setUpdatedBy(operatorId);
object.setUpdatedAt(LocalDateTime.now());
@@ -139,11 +157,17 @@ public class MeasurementImageService {
}
private FileObjectEntity requireObject(long tenantId, long inboundId, long fileId) {
return requireObject(tenantId, inboundId, fileId,
Set.of("INBOUND_WEIGHT_IMAGE", "INBOUND_MEASUREMENT_IMAGE"));
}
private FileObjectEntity requireObject(long tenantId, long businessId, long fileId,
Set<String> businessTypes) {
FileObjectEntity object = files.selectOne(new LambdaQueryWrapper<FileObjectEntity>()
.eq(FileObjectEntity::getTenantId, tenantId)
.eq(FileObjectEntity::getId, fileId)
.in(FileObjectEntity::getBusinessType, "INBOUND_WEIGHT_IMAGE", "INBOUND_MEASUREMENT_IMAGE")
.eq(FileObjectEntity::getBusinessId, Long.toString(inboundId))
.in(FileObjectEntity::getBusinessType, businessTypes)
.eq(FileObjectEntity::getBusinessId, Long.toString(businessId))
.eq(FileObjectEntity::getStatus, "ACTIVE")
.eq(FileObjectEntity::getDeleted, false));
if (object == null) throw new BizException(ResultCode.NOT_FOUND, "称重图片不存在");
@@ -59,4 +59,18 @@ class InternalMeasurementImageControllerTest {
assertThat(response.getBody()).containsExactly(1, 2, 3);
verify(images).download(11L, 21L, 41L);
}
@Test
void storesAnUnmatchedMojiaReceiptImageWithoutAnInboundId() {
var file = new MockMultipartFile("file", "mojia.jpg", MediaType.IMAGE_JPEG_VALUE,
new byte[]{1, 2, 3});
var result = new MeasurementImageService.UploadResult(41L, "mojia.jpg",
MediaType.IMAGE_JPEG_VALUE, 3L, "NOT_REQUIRED", null, null, null, null, null);
when(images.uploadMojiaReceiptEvidence(11L, 51L, 31L, file)).thenReturn(result);
var response = controller.uploadReceipt("internal-token", 31L, 11L, 51L, file);
assertThat(response.data().fileId()).isEqualTo(41L);
verify(images).uploadMojiaReceiptEvidence(11L, 51L, 31L, file);
}
}
@@ -10,6 +10,7 @@ import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
@@ -70,6 +71,29 @@ class MeasurementImageServiceEvidenceTest {
verifyNoInteractions(ocr);
}
@Test
void storesUnmatchedMojiaReceiptUnderItsOwnBusinessType() {
var image = new MockMultipartFile("file", "mojia.jpg", MediaType.IMAGE_JPEG_VALUE,
new byte[]{1, 2, 3});
when(storage.store(11L, 51L, image)).thenReturn(new MeasurementImageStorage.Stored(
"measurements/11/51/mojia.jpg", Path.of("mojia.jpg"), MediaType.IMAGE_JPEG_VALUE,
3L, "hash", "mojia.jpg"));
ArgumentCaptor<FileObjectEntity> object = ArgumentCaptor.forClass(FileObjectEntity.class);
doAnswer(invocation -> {
FileObjectEntity value = invocation.getArgument(0);
value.setId(41L);
return 1;
}).when(files).insert(any(FileObjectEntity.class));
var result = service.uploadMojiaReceiptEvidence(11L, 51L, 31L, image);
assertThat(result.fileId()).isEqualTo(41L);
verify(files).insert(object.capture());
assertThat(object.getValue().getBusinessType()).isEqualTo("MOJIA_MEASUREMENT_RECEIPT");
assertThat(object.getValue().getBusinessId()).isEqualTo("51");
verifyNoInteractions(ocr);
}
@Test
void shouldHandleCase02() {
FileObjectEntity object = new FileObjectEntity();