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

Как добавить движения к документу

Когда нужны движения

Движения нужны, если документ или его позиции изменяют состояние склада:

  • Приход/расход товара на складских местах → StorageRegistryMovement
  • Контейнер перемещается → ContainerMovement
  • Серийный номер перемещается → SerialNumberMovement
  • Товар вошёл/вышел из рабочего процесса → WorkflowRegistryMovement
  • Блокировка операции → ControlRegistryMovement
  • Товар прошёл этап отгрузки → ShipmentControlRegistryMovement

Реализация на уровне документа

Вариант 1: Движения генерирует документ

Реализовать GenerateMovements в классе документа:

@Entity
public class MyNewDoc extends BaseDoc<MyNewDocStatus> implements GenerateMovements {

@Override
public List<Movement<?, ?>> getMovements() {
if (getStatus() != MyNewDocStatus.DONE) {
return Collections.emptyList();
}
return List.of(
StorageRegistryMovement.builder()
.document(this)
.dateTime(LocalDateTime.now())
.coordinateFrom(null) // приход (нет источника)
.coordinateTo(new StorageRegistryCoordinate(
getSkuPackaging().getId(),
getSkuBatch() != null ? getSkuBatch().getId() : null,
getCondition(),
getWarehouseSpace(),
getId()
))
.quantity(getQuantity())
.build()
);
}

@Override
public Set<Class<? extends Movement<?, ?>>> getMovementTypes() {
return Set.of(StorageRegistryMovement.class);
}
}

Вариант 2: Движения генерируют позиции

Реализовать HasItemsWithMovements в документе и GenerateMovements в позиции:

@Entity
public class MyNewDoc extends BaseDoc<MyNewDocStatus> implements HasItemsWithMovements {
// getMovementsFromItems() агрегирует движения из всех неудалённых позиций
}

@Entity
public class MyNewDocItem extends BaseDocItem<MyNewDoc, MyNewDocStatus> implements GenerateMovements {
@Override
public List<Movement<?, ?>> getMovements() {
// движения на уровне позиции
}

@Override
public Set<Class<? extends Movement<?, ?>>> getMovementTypes() {
return Set.of(StorageRegistryMovement.class);
}
}

Типы движений и их координаты

StorageRegistryMovement (остатки)

StorageRegistryMovement.builder()
.document(doc)
.dateTime(LocalDateTime.now())
.coordinateFrom(fromCoord) // null = приход
.coordinateTo(toCoord) // null = расход
.quantity(BigDecimal.valueOf(10))
.build();

Координата: StorageRegistryCoordinate(skuPackagingId, skuBatchId, condition, warehouseSpace, sourceDocumentId)

ContainerMovement (контейнеры)

ContainerMovement.builder()
.document(doc)
.dateTime(LocalDateTime.now())
.object(container)
.coordinateFrom(oldCoord)
.coordinateTo(newCoord)
.build();

Координата: ContainerCoordinate(reserved, warehouseSpace, reservedForDocumentId)

WorkflowRegistryMovement (процессы)

Координата: WorkflowRegistryCoordinate(containerId, skuPackagingId, skuBatchId, condition, warehouseSpaceId, workflowTaskId)

Важные правила

  1. Движения генерируются на основе текущего состояния документа/позиции. При повторном сохранении старые движения автоматически откатываются.

  2. Обе координаты (from и to) — двойная проводка: вычитает из "откуда", прибавляет к "куда". Если одна координата null — одинарная проводка.

  3. Не генерируйте движения для удалённых документов/позиций — MovementManager автоматически пропускает их.

  4. Блокировки проверяются автоматическиcheckMovementIsNotBlocked вызывается для StorageRegistryMovement и ContainerMovement перед применением.

  5. Валидация неотрицательностиquantity >= reserved и quantity >= 0 проверяются автоматически после применения движений.

  6. Модификаторы — при необходимости можно отключить обработку движений через DISABLE_MOVEMENTS_PROCESSING или форсировать через FORCE_MOVEMENTS_PROCESSING.