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

arm_blocking

Руководство по реализации механизма блокировки/разблокировки (Backend)

Цель: реализовать систему блокировок/разблокировок, позволяющую ограничивать выполнение операций (приемка, отбор, хранение и пр.) на основании установленных бизнес-правил. StateMachine не используется. Работа идёт через прямые сервисные вызовы и кеширование.


1. Бизнес-функциональность

Механизм должен обеспечивать:

  • Гибкий выбор сущностей, на которые накладывается блокировка (ячейка, место, ряд, зона, склад, контейнер, SKU, партия, группа и пр.)
  • Выбор причины блокировки (из справочника)
  • Привязка к операциям (приемка, отбор, перемещение и др.)
  • Возможность множественного выбора
  • История изменений (аудит)

1.2 Бизнес‑процесс (sequence‑diag)


2. Структура данных и персистенция

2.1. Документы

СущностьНазначениеКлючевые поля
BlockingDocЗаголовок документа блокировкиid (UUID), docNumber, docDate, status, reasonId, comment, responsibleEmployeeId, createdAt
BlockingDocItemСтроки документа (конкретные объекты)id, docId (FK), type, referenceId, operations (JSON/SET), createdAt
BlockingReasonСправочник причинid, code, description, isActive

Все сущности помечаем @Audited для сохранения истории.

2.2. Дополнительные справочники / enum’ы

public enum BlockingObjectType { SKU, SKU_BATCH, CELL, PLACE, ROW, ZONE, WAREHOUSE, CONTAINER, ORG, GROUP, CATEGORY }
public enum BlockedOperation { RECEIPT, PLACEMENT, PICKING, MOVEMENT, INVENTORY, SHIPMENT }

3. REST‑API (черновик)

HTTPEndpointDTO / ПараметрыОписание
GET/blocking/reasonsСписок причин блокировки
GET/blockingtype, referenceId, operation?Активные блокировки по фильтру
POST/blockingBlockingDocCreateDtoСоздать/обновить блокировку

4. Проверка блокировок при движениях (MovementManager)

@Component
@RequiredArgsConstructor
public class MovementManager {

private final BlockingCache blockingCache;

public void checkBlocked(MovementCandidate c, Operation op) {
if (blockingCache.isBlocked(BlockingObjectType.SKU, c.getSkuId(), op)) {
throw new BlockingException("SKU «" + c.getSkuId() + "» заблокирован для операции " + op);
}
// ... аналогично для ячейки, контейнера и др.
}
}

Кеш заполняется BlockingCacheLoader из таблиц blocking_doc_item, фильтр status = ACTIVE.


5. Пример контроллера (аннотации по корпоративному стандарту)

@RestController
@RequestMapping("/blocking")
@RequiredArgsConstructor
@Tag(name = "Блокировки", description = "Документы блокировки/разблокировки")
public class BlockingController {

private final BlockingService blockingService;

@Operation(summary = "Получить причины блокировок")
@GetMapping("/reasons")
public List<BlockingReasonDto> getReasons() {
return blockingService.getReasons();
}

@Operation(summary = "Создать документ блокировки")
@PostMapping
@RetryIfOptimisticLock(retries = 3)
public UUID createDoc(@RequestBody @Valid BlockingDocCreateDto dto) {
return blockingService.createDoc(dto);
}

@Operation(summary = "Получить документ блокировки")
@GetMapping("/docs/{id}")
@RequireAnyPrivilege({Privilege.BLOCKING_VIEW})
public BlockingDocDto getDoc(@PathVariable UUID id) {
return blockingService.getDoc(id);
}

@Operation(summary = "Проверка активных блокировок")
@GetMapping
@RequireAnyPrivilege({Privilege.BLOCKING_VIEW})
public List<BlockingRuleDto> getRules(@RequestParam BlockingObjectType type,
@RequestParam String referenceId,
@RequestParam(required = false) BlockedOperation operation) {
return blockingService.getRules(type, referenceId, operation);
}
}

6. Тестирование & отладка (кратко)

  • Unit: BlockingService, BlockingCacheLoader (смотри пример в @cash_loader), MovementManager.checkBlocked().
  • Integration: полный цикл создания документа → кеш → запрет движения.
  • Liquibase‑миграции: таблицы blocking_doc, blocking_doc_item, blocking_reason, индексы на (type, referenceId) и status.

NB: при появлении необходимости «разблокировок» как отдельной сущности — ввести статус INACTIVE для blocking_doc и переключать в одном документе.

6. Миграции

7. Тестирование & отладка