From 3cc7410ab5c8eaf0e35d93da71ba5b140a856ff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=97=D0=B8=D0=BC=D0=B8=D0=BD?= Date: Mon, 22 Jun 2026 10:00:38 +0000 Subject: [PATCH] =?UTF-8?q?fix(record):=20reopen=20latest=20closed=20?= =?UTF-8?q?=D0=BF=D0=BE=20max(validTo)=20+=20revert=20i18n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/record/RecordHistoryDrawer.tsx | 2 +- .../service/DictionaryRecordService.java | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/ordinis-admin-ui/src/components/record/RecordHistoryDrawer.tsx b/ordinis-admin-ui/src/components/record/RecordHistoryDrawer.tsx index 83144ac..8c72562 100644 --- a/ordinis-admin-ui/src/components/record/RecordHistoryDrawer.tsx +++ b/ordinis-admin-ui/src/components/record/RecordHistoryDrawer.tsx @@ -158,7 +158,7 @@ export const RecordHistoryDrawer = ({ onClick={() => onRevert(rec.data, displayVersion)} leftIcon={} > - {t('history.revert', { defaultValue: `Откатить к v${displayVersion}` })} + {t('history.revert', { v: displayVersion, defaultValue: `Откатить к v${displayVersion}` })} )} {isLatest && status === 'expired' && onReopen && ( diff --git a/ordinis-rest-api/src/main/java/cloud/nstart/terravault/ordinis/restapi/service/DictionaryRecordService.java b/ordinis-rest-api/src/main/java/cloud/nstart/terravault/ordinis/restapi/service/DictionaryRecordService.java index 890d2b5..c15ed38 100644 --- a/ordinis-rest-api/src/main/java/cloud/nstart/terravault/ordinis/restapi/service/DictionaryRecordService.java +++ b/ordinis-rest-api/src/main/java/cloud/nstart/terravault/ordinis/restapi/service/DictionaryRecordService.java @@ -441,17 +441,18 @@ public class DictionaryRecordService { "business_key='" + businessKey + "' уже активен — re-open не нужен."); } - // findByDictionaryIdAndBusinessKeyOrderByValidFromDesc возвращает версии - // отсортированными по validFrom desc. Самая недавно закрытая — это первый - // элемент, у которого validTo != FAR_FUTURE (closed) и validTo больше всех - // других closed. Так как версии bitemporal не пересекаются (exclusion - // constraint), а closed period — последний по validFrom, то это и есть - // первый matching элемент. + // Самая недавно закрытая версия = max(validTo) среди closed с НЕпустым + // периодом. Берём по validTo, а не по validFrom: при UPDATE/CLOSE в один + // момент возможны zero-length версии (validFrom == validTo) — артефакты, + // которые "никогда не были активны". Их фильтруем (isAfter validFrom), + // иначе re-open восстановил бы пустую версию вместо реально закрытой. var prevData = repository .findByDictionaryIdAndBusinessKeyOrderByValidFromDesc(d.getId(), businessKey) .stream() - .filter(r -> r.getValidTo() != null && r.getValidTo().isBefore(FAR_FUTURE)) - .findFirst() + .filter(r -> r.getValidTo() != null + && r.getValidTo().isBefore(FAR_FUTURE) + && r.getValidTo().isAfter(r.getValidFrom())) + .max(java.util.Comparator.comparing(DictionaryRecord::getValidTo)) .orElseThrow(() -> OrdinisException.notFound( "record_not_closed", "Нет закрытой версии business_key='" + businessKey + "' для re-open."));