chore: сохранение текущих изменений (deploy local-bundle/local-jar, request-service, slots UI markedObjectIds)
Безопасный коммит незакоммиченной работы перед реализацией бронирования слотов. Вне коммита оставлены: package-lock.json (пустой, мусор), requests.json (scratch-данные).
This commit is contained in:
+4
-1
@@ -67,7 +67,10 @@ data class CreateRequestDto(
|
||||
*/
|
||||
@field:DecimalMin(value = "0", inclusive = false)
|
||||
@field:DecimalMax("100")
|
||||
val coverageRequiredPercent: Double = 100.0,
|
||||
// Nullable с дефолтом: под Jackson 3 (MVC) FAIL_ON_NULL_FOR_PRIMITIVES включён по умолчанию,
|
||||
// и явный null от клиента падал бы на примитиве Double. Опциональное поле с дефолтом трактуем
|
||||
// null как «взять дефолт» — при маппинге в сущность коалесим к 100.0.
|
||||
val coverageRequiredPercent: Double? = 100.0,
|
||||
|
||||
/** Параметры оптической съемки. */
|
||||
@field:Valid
|
||||
|
||||
+1
-1
@@ -81,7 +81,7 @@ class RequestService(
|
||||
remainingArea = geometryArea,
|
||||
// Порог завершения по покрытию приходит из API (по умолчанию 100%); это прогресс съёмки,
|
||||
// в отличие от importance — важности заявки для карты приоритетов ячеек.
|
||||
coverageRequiredPercent = request.coverageRequiredPercent,
|
||||
coverageRequiredPercent = request.coverageRequiredPercent ?: 100.0,
|
||||
importance = importance,
|
||||
beginDateTime = request.beginDateTime,
|
||||
endDateTime = request.endDateTime,
|
||||
|
||||
+18
-1
@@ -112,6 +112,23 @@ class RequestServiceTest {
|
||||
assertEquals(80.0, persistedRequests.single().coverageRequiredPercent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `create request coalesces null coverage required percent to default`() {
|
||||
val persistedRequests = mutableListOf<RequestEntity>()
|
||||
doReturn(false)
|
||||
.`when`(requestRepository)
|
||||
.existsById(REQUEST_ID)
|
||||
doAnswer { invocation ->
|
||||
persistedRequests += invocation.getArgument<RequestEntity>(0)
|
||||
null
|
||||
}.`when`(entityManager).persist(any(RequestEntity::class.java))
|
||||
|
||||
// Явный null (клиент прислал coverageRequiredPercent: null) трактуем как «взять дефолт».
|
||||
service.createRequest(createRequestDto(coverageRequiredPercent = null))
|
||||
|
||||
assertEquals(100.0, persistedRequests.single().coverageRequiredPercent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `get request by id returns name and importance`() {
|
||||
val request = existingRequest()
|
||||
@@ -296,7 +313,7 @@ class RequestServiceTest {
|
||||
verify(requestCellRepository).deleteByRequestId(REQUEST_ID)
|
||||
}
|
||||
|
||||
private fun createRequestDto(coverageRequiredPercent: Double = 100.0): CreateRequestDto {
|
||||
private fun createRequestDto(coverageRequiredPercent: Double? = 100.0): CreateRequestDto {
|
||||
return CreateRequestDto(
|
||||
id = REQUEST_ID,
|
||||
name = REQUEST_NAME,
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
ARG RUNTIME_IMAGE=repo.nstart.cloud/bellsoft/liberica-openjre-alpine:21.0.5
|
||||
FROM ${RUNTIME_IMAGE}
|
||||
|
||||
ENV JAVA_OPTS=""
|
||||
|
||||
ADD ./build/libs/*.jar /app.jar
|
||||
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app.jar"]
|
||||
@@ -24,6 +24,8 @@ type Tgu2DMapViewProps = {
|
||||
selectedRequestPolygonId?: string;
|
||||
/** Внешняя подсветка объекта (контур съёмки или линия сброса) по его id — «как будто кликнули на карте». */
|
||||
selectedObjectId?: string;
|
||||
/** Отмеченные объекты (постоянная подсветка независимо от выбора) — напр. слоты, отобранные под бронь. */
|
||||
markedObjectIds?: ReadonlySet<string>;
|
||||
/** Точка, на которую центрировать карту; перецентрирование срабатывает при смене focusToken. */
|
||||
focusPoint?: GeoPoint;
|
||||
focusToken?: number;
|
||||
@@ -84,6 +86,7 @@ export function Tgu2DMapView({
|
||||
hoveredRequestPolygonId,
|
||||
selectedRequestPolygonId,
|
||||
selectedObjectId,
|
||||
markedObjectIds,
|
||||
focusPoint,
|
||||
focusToken,
|
||||
enableObjectChooser = false,
|
||||
@@ -529,6 +532,14 @@ export function Tgu2DMapView({
|
||||
[hoveredRequestPolygonId, activePolygons]
|
||||
);
|
||||
const overlayPolygons = uniquePolygons([externalSelectedPolygon, selectedPolygon, externalHoveredPolygon, hoverRef.current]);
|
||||
// Отмеченные объекты (напр. отобранные под бронь слоты) — постоянная подсветка под обычным выбором.
|
||||
const markedPolygons = useMemo(
|
||||
() =>
|
||||
markedObjectIds && markedObjectIds.size > 0
|
||||
? activePolygons.filter((p) => markedObjectIds.has(p.id))
|
||||
: [],
|
||||
[markedObjectIds, activePolygons]
|
||||
);
|
||||
const overlayMarkers = uniqueMarkers([selectedMarker, hoverMarkerRef.current]);
|
||||
|
||||
// Накопленное расстояние (км) от первой вершины линейки до каждой — по дуге большого круга.
|
||||
@@ -563,6 +574,24 @@ export function Tgu2DMapView({
|
||||
>
|
||||
<canvas className="tgu-2d-map__canvas" ref={canvasRef} />
|
||||
<svg className="tgu-2d-map__overlay" width={size.width} height={size.height} aria-hidden="true">
|
||||
{markedPolygons.flatMap((polygon) => {
|
||||
const raw = polygon.points.map((point) => projection.project(point));
|
||||
const pts = unwrapX(raw, projection.worldSize);
|
||||
return ([0, projection.worldSize, -projection.worldSize] as const).flatMap((dx) => {
|
||||
const visible = pts.some((p) => {
|
||||
const x = p.x + dx;
|
||||
return x >= -64 && x <= size.width + 64 && p.y >= -64 && p.y <= size.height + 64;
|
||||
});
|
||||
if (!visible) return [];
|
||||
return (
|
||||
<polygon
|
||||
key={`marked:${polygon.id}:${dx}`}
|
||||
points={pts.map((p) => `${p.x + dx},${p.y}`).join(" ")}
|
||||
className="is-marked"
|
||||
/>
|
||||
);
|
||||
});
|
||||
})}
|
||||
{overlayPolygons.flatMap((polygon) => {
|
||||
const raw = polygon.points.map((point) => projection.project(point));
|
||||
const pts = unwrapX(raw, projection.worldSize);
|
||||
|
||||
@@ -113,6 +113,12 @@ export function TguSlotsTab({ range }: TguSlotsTabProps) {
|
||||
// Ключи забронированных слотов — для бейджа «бронь» в таблице и отдельного слоя на карте.
|
||||
const bookedKeys = useMemo(() => new Set(bookedSlots.map(slotKey)), [bookedSlots]);
|
||||
|
||||
// id полигонов слотов, отобранных галочками под бронь — постоянная подсветка на карте.
|
||||
const markedSlotIds = useMemo(
|
||||
() => new Set(Array.from(selectedKeys, (key) => `slot:${key}`)),
|
||||
[selectedKeys]
|
||||
);
|
||||
|
||||
// Слот по ключу — чтобы из выбора на карте (id "slot:key"/"booked:key") восстановить полные данные.
|
||||
const slotByKey = useMemo(() => {
|
||||
const map = new Map<string, SlotDTO>();
|
||||
@@ -758,6 +764,7 @@ export function TguSlotsTab({ range }: TguSlotsTabProps) {
|
||||
onObjectsAtPoint={handleObjectsAtPoint}
|
||||
onObjectSelect={handleObjectSelect}
|
||||
selectedObjectId={hasRequest ? activeSlotId : activeHitId}
|
||||
markedObjectIds={hasRequest ? markedSlotIds : undefined}
|
||||
focusPoint={focusPoint}
|
||||
focusToken={focusToken}
|
||||
/>
|
||||
|
||||
@@ -1175,6 +1175,14 @@
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
/* Отобранные под бронь слоты — постоянная зелёная подсветка (отличается от оранжевого выбора). */
|
||||
.tgu-2d-map__overlay polygon.is-marked {
|
||||
fill: rgba(58, 209, 122, 0.22);
|
||||
stroke: #3ad17a;
|
||||
stroke-width: 2;
|
||||
stroke-dasharray: 6 4;
|
||||
}
|
||||
|
||||
.tgu-2d-map__overlay polyline.is-selected {
|
||||
stroke: #f0ad2e;
|
||||
stroke-width: 7;
|
||||
|
||||
Reference in New Issue
Block a user