Перейти к основному содержимому

code_skeleton

Этот файл содержит заготовки основных классов и компонентов модуля сортировки, по аналогии с модулем "Picking".


1. Controller: SortingController

@Slf4j
@Validated
@RestController
@RequestMapping("/sorting")
public class SortingController {
private final SortingDocEntityService sortingDocEntityService;
private final StockService stockService;
private final StateMachineFacade<SortingState, SortingEvent> stateMachineFacade;
private final RequiredActionUtil requiredActionUtil;

public SortingController(SortingDocEntityService sortingDocEntityService,
StockService stockService,
StateMachineFacade<SortingState,SortingEvent> stateMachineFacade,
RequiredActionUtil requiredActionUtil) {
this.sortingDocEntityService = sortingDocEntityService;
this.stockService = stockService;
this.stateMachineFacade = stateMachineFacade;
this.requiredActionUtil = requiredActionUtil;
}

@GetMapping("/search")
public ResponseEntity<SortingSearchResponseDto> search(@RequestParam(required=false) String filter) {
List<SortingSearchItemDto> items = sortingDocEntityService.search(filter);
SortingSearchResponseDto dto = SortingSearchResponseDto.builder()
.items(items)
.build();
return ResponseEntity.ok(dto);
}

@PostMapping("/{batchId}/join")
public ResponseEntity<SortingResponseDto> join(@PathVariable UUID batchId) {
SortingResponseDto dto = stateMachineFacade.executeWithStateMachine(batchId, sm -> {
sm.sendEvent(SortingEvent.JOIN);
return requiredActionUtil.getResponse(sm, SortingResponseDto.class);
});
return ResponseEntity.ok(dto);
}

@PostMapping("/{batchId}/enter-barcode")
public ResponseEntity<SortingResponseDto> enterBarcode(@PathVariable UUID batchId,
@RequestBody EnterBarcodeRequestDto req) {
SortingResponseDto dto = stateMachineFacade.executeWithStateMachine(batchId, sm -> {
sm.sendEvent(SortingEvent.ENTER_BARCODE, req.getBarcode());
return requiredActionUtil.getResponse(sm, SortingResponseDto.class);
});
return ResponseEntity.ok(dto);
}

@PostMapping("/{batchId}/enter-sku-batch")
public ResponseEntity<SortingResponseDto> enterSkuBatch(@PathVariable UUID batchId,
@RequestBody EnterSkuBatchRequestDto req) {
SortingResponseDto dto = stateMachineFacade.executeWithStateMachine(batchId, sm -> {
sm.sendEvent(SortingEvent.ENTER_SKU_BATCH, req.getSkuBatchId());
return requiredActionUtil.getResponse(sm, SortingResponseDto.class);
});
return ResponseEntity.ok(dto);
}

@PostMapping("/{batchId}/enter-quantity")
public ResponseEntity<SortingResponseDto> enterQuantity(@PathVariable UUID batchId,
@RequestBody EnterQuantityRequestDto req) {
SortingResponseDto dto = stateMachineFacade.executeWithStateMachine(batchId, sm -> {
sm.sendEvent(SortingEvent.ENTER_QUANTITY, req.getQuantity());
return requiredActionUtil.getResponse(sm, SortingResponseDto.class);
});
return ResponseEntity.ok(dto);
}

@PostMapping("/{batchId}/enter-serial-number")
public ResponseEntity<SortingResponseDto> enterSerialNumber(@PathVariable UUID batchId,
@RequestBody EnterSerialNumberRequestDto req) {
SortingResponseDto dto = stateMachineFacade.executeWithStateMachine(batchId, sm -> {
sm.sendEvent(SortingEvent.ENTER_SERIAL_NUMBER, req.getSerialNumber());
return requiredActionUtil.getResponse(sm, SortingResponseDto.class);
});
return ResponseEntity.ok(dto);
}

@PostMapping("/{batchId}/item-not-available")
public ResponseEntity<SortingResponseDto> reportIssue(@PathVariable UUID batchId,
@RequestBody ItemNotAvailableRequestDto req) {
SortingResponseDto dto = stateMachineFacade.executeWithStateMachine(batchId, sm -> {
sm.sendEvent(SortingEvent.ITEM_NOT_AVAILABLE, req.getReason());
return requiredActionUtil.getResponse(sm, SortingResponseDto.class);
});
return ResponseEntity.ok(dto);
}

@PostMapping("/{batchId}/upload-issue-photo")
public ResponseEntity<SortingResponseDto> uploadPhoto(@PathVariable UUID batchId,
@RequestBody IssuePhotoRequestDto req) {
// TODO: сохранить фото
SortingResponseDto dto = SortingResponseDto.builder().build();
return ResponseEntity.ok(dto);
}

@PostMapping("/{batchId}/leave")
public ResponseEntity<SortingResponseDto> leave(@PathVariable UUID batchId) {
SortingResponseDto dto = stateMachineFacade.executeWithStateMachine(batchId, sm -> {
sm.sendEvent(SortingEvent.LEAVE);
return requiredActionUtil.getResponse(sm, SortingResponseDto.class);
});
return ResponseEntity.ok(dto);
}

@PostMapping("/{batchId}/cancel")
public ResponseEntity<SortingResponseDto> cancel(@PathVariable UUID batchId) {
SortingResponseDto dto = stateMachineFacade.executeWithStateMachine(batchId, sm -> {
sm.sendEvent(SortingEvent.CANCEL);
return requiredActionUtil.getResponse(sm, SortingResponseDto.class);
});
return ResponseEntity.ok(dto);
}
}

2. Entity + Service

@Entity
@Table(name="sorting_doc")
public class SortingDoc {
@Id
private UUID id;
private String batch;
private SortingStatus status;
private LocalDateTime createdAt;
@OneToMany(mappedBy="document")
private List<SortingDocFactItem> items;
// getters/setters
}
public interface SortingDocEntityService {
Optional<SortingDoc> findByBatch(String batch);
List<SortingDoc> findAll();
SortingDoc save(SortingDoc doc);
SortingDoc update(SortingDoc doc);
void deleteById(UUID id);
}

3. Промежуточная модель: UnsavedSortingDocFactItem

@Getter
@Setter
public class UnsavedSortingDocFactItem extends BaseUnsavedDocItem<SortingDocFactItem, SortingDoc> {
private UUID skuId;
private UUID skuBatchId;
private Integer quantity;
private String serialNumber;

public SortingDocFactItem toEntity() {
SortingDocFactItem entity = new SortingDocFactItem();
entity.setDocument(getDocument());
entity.setSkuId(skuId);
entity.setSkuBatchId(skuBatchId);
entity.setQuantity(BigDecimal.valueOf(quantity));
entity.setSerialNumber(serialNumber);
return entity;
}
// стандартные геттеры/сеттеры генерируются Lombok
}

4. State Machine Config

@Configuration
@EnableStateMachineFactory(name="sortingStateMachineFactory")
public class SortingStateMachineConfig
extends EnumStateMachineConfigurerAdapter<SortingState, SortingEvent> {

@Override
public void configure(StateMachineStateConfigurer<SortingState,SortingEvent> states) throws Exception {
states.withStates()
.initial(SortingState.INIT)
.state(SortingState.AWAITING_SORTER)
.state(SortingState.AWAITING_CONTAINER)
.state(SortingState.AWAITING_SKU)
.state(SortingState.AWAITING_BATCH)
.state(SortingState.AWAITING_QTY)
.state(SortingState.AWAITING_SERIAL)
.state(SortingState.REVIEW)
.end(SortingState.END);
}

@Override
public void configure(StateMachineTransitionConfigurer<SortingState,SortingEvent> transitions) throws Exception {
transitions.withExternal()
.source(SortingState.INIT).target(SortingState.AWAITING_SORTER).event(SortingEvent.JOIN)
.and().withExternal()
.source(SortingState.AWAITING_SORTER).target(SortingState.AWAITING_CONTAINER).event(SortingEvent.ENTER_SORTER)
.and().withExternal()
.source(SortingState.AWAITING_CONTAINER).target(SortingState.AWAITING_SKU).event(SortingEvent.ENTER_BARCODE)
.and().withExternal()
.source(SortingState.AWAITING_SKU).target(SortingState.AWAITING_BATCH).event(SortingEvent.ENTER_SKU_BATCH)
.and().withExternal()
.source(SortingState.AWAITING_BATCH).target(SortingState.AWAITING_QTY).event(SortingEvent.ENTER_QUANTITY)
.and().withExternal()
.source(SortingState.AWAITING_QTY).target(SortingState.AWAITING_SERIAL).event(SortingEvent.ENTER_SERIAL_NUMBER)
.and().withExternal()
.source(SortingState.AWAITING_SERIAL).target(SortingState.REVIEW).event(SortingEvent.ITEM_NOT_AVAILABLE)
.and().withExternal()
.source(SortingState.REVIEW).target(SortingState.END).event(SortingEvent.LEAVE)
.and().withExternal()
.source(SortingState.AWAITING_CONTAINER).target(SortingState.INIT).event(SortingEvent.CANCEL);
}
}

5. Документация и миграции

  • Создать SQL-миграцию для таблиц sorting_doc и sorting_doc_fact_item.

  • Описать DTO:

    • SortingSearchResponseDto + SortingSearchItemDto

    • SortingResponseDto

    • EnterBarcodeRequestDto, EnterSkuBatchRequestDto, EnterQuantityRequestDto, EnterSerialNumberRequestDto, ItemNotAvailableRequestDto, IssuePhotoRequestDto.


Это готовый шаблон модуля сортировки — осталось добавить бизнес-логику и протестировать.