Skip to content

Commit 4347de1

Browse files
fbarretadiego-oxd
andauthored
chore: orca assets endpoints (#1770)
* chore: asset management backend * chore: undo test changes * chore: added filters and pagination to findAllAssets * chore: update asset refactor * chore: refactor bulk asset response * chore: select fix and bulk refactor * chore: code review fixes * chore: Add missing fields and endpoints (#1773) * chore: Add missing fields and endpoints * chore: Add tests --------- Co-authored-by: diego-oxd <107205353+diego-oxd@users.noreply.github.com>
1 parent 89c6a7e commit 4347de1

14 files changed

Lines changed: 4025 additions & 0 deletions
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
-- Get lat/long coordinates for recreation assets with point geometry.
2+
-- Used by: GET /v1/assets and GET /v1/assets/:id (asset coordinate lookup).
3+
-- Coordinates are transformed from BC Albers (SRID 3005) to WGS84 (SRID 4326).
4+
-- Batched by asset_id array so list responses don't issue one query per row.
5+
-- Returns one row per asset_id that has a recreation_asset_geom record with geometry set.
6+
7+
select
8+
rag.asset_id,
9+
rag.geometry_type_code,
10+
round(public.st_y(public.st_transform(
11+
case
12+
when public.st_srid(rag.geometry) = 0
13+
then public.st_setsrid(rag.geometry, 3005)
14+
else rag.geometry
15+
end, 4326))::numeric, 6) as latitude,
16+
round(public.st_x(public.st_transform(
17+
case
18+
when public.st_srid(rag.geometry) = 0
19+
then public.st_setsrid(rag.geometry, 3005)
20+
else rag.geometry
21+
end, 4326))::numeric, 6) as longitude
22+
from rst.recreation_asset_geom rag
23+
where rag.asset_id = any ($1::bigint[])
24+
and rag.geometry is not null;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
2+
/* eslint-disable */
3+
// biome-ignore-all lint: generated file
4+
// @ts-nocheck
5+
import * as $runtime from '@prisma/client/runtime/client';
6+
7+
/**
8+
* @param _int8
9+
*/
10+
export const getRecreationAssetGeom = $runtime.makeTypedQueryFactory(
11+
'\nselect\nrag.asset_id,\nrag.geometry_type_code,\nround(public.st_y(public.st_transform(\ncase\nwhen public.st_srid(rag.geometry) = 0\nthen public.st_setsrid(rag.geometry, 3005)\nelse rag.geometry\nend, 4326))::numeric, 6) as latitude,\nround(public.st_x(public.st_transform(\ncase\nwhen public.st_srid(rag.geometry) = 0\nthen public.st_setsrid(rag.geometry, 3005)\nelse rag.geometry\nend, 4326))::numeric, 6) as longitude\nfrom rst.recreation_asset_geom rag\nwhere rag.asset_id = any ($1::bigint[])\nand rag.geometry is not null;',
12+
) as (
13+
_int8: (number | bigint)[],
14+
) => $runtime.TypedSql<
15+
getRecreationAssetGeom.Parameters,
16+
getRecreationAssetGeom.Result
17+
>;
18+
19+
export namespace getRecreationAssetGeom {
20+
export type Parameters = [_int8: (number | bigint)[]];
21+
export type Result = {
22+
asset_id: bigint;
23+
geometry_type_code: string | null;
24+
latitude: $runtime.Decimal | null;
25+
longitude: $runtime.Decimal | null;
26+
};
27+
}

admin/backend/src/recreation-resources/recreation-resource.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { ExportsModule } from './exports/exports.module';
1717
import { ReservationModule } from './reservation/reservation.module';
1818
import { TrailsModule } from './trails/trails.module';
1919
import { AdvisoriesModule } from './advisories/advisories.module';
20+
import { RecreationAssetModule } from '@/resource-asset/resource-asset.module';
2021

2122
@Module({
2223
imports: [
@@ -34,6 +35,7 @@ import { AdvisoriesModule } from './advisories/advisories.module';
3435
ReservationModule,
3536
TrailsModule,
3637
AdvisoriesModule,
38+
RecreationAssetModule,
3739
],
3840
controllers: [RecreationResourceController],
3941
providers: [
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './resource-asset-code.dto';
2+
export * from './resource-asset-repair.dto';
3+
export * from './resource-asset.dto';
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
2+
import { IsInt, IsOptional, IsString, MaxLength } from 'class-validator';
3+
4+
export class RecreationAssetCodeDto {
5+
@ApiProperty({
6+
description: 'Surrogate primary key for the asset type code',
7+
example: 1,
8+
})
9+
@IsInt()
10+
asset_code: number;
11+
12+
@ApiPropertyOptional({
13+
description: 'Description of the asset type',
14+
example: 'Table - log',
15+
})
16+
@IsString()
17+
@MaxLength(120)
18+
@IsOptional()
19+
description?: string;
20+
}
21+
22+
export class RecreationRepairCodeDto {
23+
@ApiProperty({
24+
description: 'Surrogate primary key for the remedial repair code',
25+
example: 'CL',
26+
maxLength: 2,
27+
})
28+
@IsString()
29+
@MaxLength(2)
30+
recreation_remed_repair_code: string;
31+
32+
@ApiPropertyOptional({
33+
description: 'Description of the repair code',
34+
example: 'Clean',
35+
})
36+
@IsString()
37+
@MaxLength(120)
38+
@IsOptional()
39+
description?: string;
40+
}
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
import { ApiProperty, ApiPropertyOptional, PartialType } from '@nestjs/swagger';
2+
import { Type } from 'class-transformer';
3+
import {
4+
IsArray,
5+
IsDateString,
6+
IsInt,
7+
IsNotEmpty,
8+
IsNumber,
9+
IsOptional,
10+
IsString,
11+
MaxLength,
12+
ValidateNested,
13+
} from 'class-validator';
14+
15+
/**
16+
* Base DTO containing shared repair attributes
17+
*/
18+
export class BaseRecreationAssetRepairDto {
19+
@ApiPropertyOptional({
20+
description:
21+
'Remedial repair classification code (FK to recreation_remed_repair_code)',
22+
example: 'RC',
23+
maxLength: 2,
24+
nullable: true,
25+
})
26+
@IsString()
27+
@MaxLength(2)
28+
@IsOptional()
29+
recreation_remed_repair_code?: string | null;
30+
31+
@ApiPropertyOptional({
32+
description: 'Estimated financial cost for repair',
33+
example: 450.0,
34+
nullable: true,
35+
})
36+
@IsNumber()
37+
@Type(() => Number)
38+
@IsOptional()
39+
estimated_repair_cost?: number | null;
40+
41+
@ApiPropertyOptional({
42+
description: 'Final actual cost incurred',
43+
example: 485.5,
44+
nullable: true,
45+
})
46+
@IsNumber()
47+
@Type(() => Number)
48+
@IsOptional()
49+
actual_repair_cost?: number | null;
50+
51+
@ApiPropertyOptional({
52+
description: 'Date completed (YYYY-MM-DD)',
53+
example: '2026-08-10',
54+
nullable: true,
55+
})
56+
@IsDateString()
57+
@IsOptional()
58+
repair_completed_date?: string | null;
59+
60+
@ApiPropertyOptional({
61+
description: 'Urgency/priority of repair',
62+
example: 'High',
63+
maxLength: 25,
64+
nullable: true,
65+
})
66+
@IsString()
67+
@MaxLength(25)
68+
@IsOptional()
69+
urgency?: string | null;
70+
71+
@ApiPropertyOptional({
72+
description: 'Trail segment start reference',
73+
example: 'KM 0.5',
74+
maxLength: 50,
75+
nullable: true,
76+
})
77+
@IsString()
78+
@MaxLength(50)
79+
@IsOptional()
80+
trail_segment_start?: string | null;
81+
82+
@ApiPropertyOptional({
83+
description: 'Trail segment end reference',
84+
example: 'KM 1.2',
85+
maxLength: 50,
86+
nullable: true,
87+
})
88+
@IsString()
89+
@MaxLength(50)
90+
@IsOptional()
91+
trail_segment_end?: string | null;
92+
}
93+
94+
/**
95+
* Payload for creating a new asset repair entry
96+
*/
97+
export class CreateRecreationAssetRepairDto extends BaseRecreationAssetRepairDto {
98+
@ApiPropertyOptional({
99+
description:
100+
'FK linking to the individual asset being repaired (populated automatically from URL param)',
101+
example: 1,
102+
})
103+
@IsInt()
104+
@Type(() => Number)
105+
@IsOptional()
106+
asset_id?: number;
107+
}
108+
109+
/**
110+
* Payload for updating an existing repair entry
111+
*/
112+
export class UpdateRecreationAssetRepairDto extends PartialType(
113+
CreateRecreationAssetRepairDto,
114+
) {}
115+
116+
/**
117+
* Full Read DTO returned by API queries
118+
*/
119+
export class RecreationAssetRepairDto extends BaseRecreationAssetRepairDto {
120+
@ApiProperty({
121+
description: 'Unique surrogate identifier for the repair log entry',
122+
example: 1,
123+
})
124+
@IsInt()
125+
@Type(() => Number)
126+
repair_id: number;
127+
128+
@ApiProperty({
129+
description: 'FK linking to the individual asset being repaired',
130+
example: 1,
131+
})
132+
@IsInt()
133+
@Type(() => Number)
134+
asset_id: number;
135+
136+
@ApiPropertyOptional({ description: 'Timestamp when record was created' })
137+
@IsOptional()
138+
created_at?: Date | string | null;
139+
140+
@ApiPropertyOptional({
141+
description: 'User identifier who created the record',
142+
})
143+
@IsOptional()
144+
created_by?: string | null;
145+
146+
@ApiPropertyOptional({
147+
description: 'Timestamp when record was last updated',
148+
})
149+
@IsOptional()
150+
updated_at?: Date | string | null;
151+
152+
@ApiPropertyOptional({
153+
description: 'User identifier who last updated the record',
154+
})
155+
@IsOptional()
156+
updated_by?: string | null;
157+
}
158+
159+
export class RepairChange {
160+
@ApiProperty({
161+
description: 'Cost of the repair',
162+
example: 1000.0,
163+
})
164+
@IsNumber()
165+
@IsNotEmpty()
166+
repair_cost: number;
167+
168+
@ApiProperty({
169+
description: 'Array of asset IDs to which this repair change applies',
170+
example: [1, 2, 3],
171+
})
172+
@IsArray()
173+
@IsInt({ each: true })
174+
asset_ids: number[];
175+
}
176+
177+
export class RecreationAssetBulkRepairDto {
178+
@ApiProperty({
179+
description:
180+
'Remedial repair classification code (FK to recreation_remed_repair_code)',
181+
example: 'CL',
182+
})
183+
@IsString()
184+
@IsNotEmpty()
185+
recreation_remed_repair_code: string;
186+
187+
@ApiProperty({
188+
description: 'Date when the repairs were completed',
189+
example: '2023-10-01',
190+
})
191+
@IsDateString()
192+
@IsOptional()
193+
completed_date?: string;
194+
195+
@ApiProperty({
196+
description: 'Array of repair changes to be applied across multiple assets',
197+
example: [
198+
{
199+
repair_cost: 1000.0,
200+
asset_ids: [1, 2, 3],
201+
},
202+
],
203+
})
204+
@IsArray()
205+
@ValidateNested({ each: true })
206+
@Type(() => RepairChange)
207+
changes: RepairChange[];
208+
}
209+
210+
export class BulkAssetUpdateResponseDto {
211+
@ApiProperty({
212+
description: 'Status response from Bulk Update operation',
213+
example: 'success',
214+
})
215+
status: string;
216+
@ApiProperty({
217+
description: 'Number of updated rows',
218+
example: 2,
219+
})
220+
updated_count: number;
221+
@ApiProperty({
222+
description: 'List of updated assets',
223+
example: 'success',
224+
})
225+
updated_asset_ids: number[];
226+
}

0 commit comments

Comments
 (0)