From 8b64da691604742c77ac7725877573fded70375d Mon Sep 17 00:00:00 2001 From: Alex Sorafumo Date: Mon, 2 Nov 2026 12:26:13 +1100 Subject: [PATCH] fix(desks): keep desk data on CSV import and find desks across the org - CSV import passes plain rows, so columns missing from the CSV no longer overwrite the matched desk asset with Desk defaults. - One findDeskByLegacyId helper resolves old desk ids for check-in codes, desk links, CSV import and favourites. - Check-in codes and desk links search the org zone, and desk links load the desk's building before they use it. - Asset InZone queries return nothing for an empty zone id. - The mock assets API filters on zones and builds parking per level. - queryAllAssetPages stops after 100 pages and has a typed query. --- .../src/app/desks/desks-manage.component.ts | 11 ++-- .../src/app/desks/desks-state.service.ts | 32 ++++++------ .../src/app/desks/desks-topbar.component.ts | 11 ++-- .../src/app/desks/desks.component.ts | 12 ++--- .../tests/desks/desks-state.service.spec.ts | 13 ++--- .../src/app/book/code-flow.component.ts | 22 ++++---- .../book/desk-flow-new/desk-flow.component.ts | 50 ++++++++----------- .../tests/book/code-flow.component.spec.ts | 8 +-- .../book/desk-flow-new.component.spec.ts | 17 ++++++- libs/assets/src/lib/assets.fn.ts | 15 ++++-- libs/assets/src/lib/desk-assets.fn.spec.ts | 13 +++++ libs/assets/src/lib/desk-assets.fn.ts | 18 +++++++ libs/assets/src/lib/locker-assets.fn.ts | 4 ++ libs/assets/src/lib/parking-assets.fn.ts | 2 + libs/bookings/src/lib/booking-form.service.ts | 11 +--- libs/mocks/src/lib/api/assets.data.ts | 13 +++++ libs/mocks/src/lib/api/assets.mock.ts | 20 ++++++++ 17 files changed, 174 insertions(+), 98 deletions(-) diff --git a/apps/concierge/src/app/desks/desks-manage.component.ts b/apps/concierge/src/app/desks/desks-manage.component.ts index eb43c0d8fb0..281deb7d347 100644 --- a/apps/concierge/src/app/desks/desks-manage.component.ts +++ b/apps/concierge/src/app/desks/desks-manage.component.ts @@ -388,13 +388,10 @@ export class DesksManageComponent extends AsyncHandler { try { const list = csvToJson(data) || []; this._state.addDesks( - list.map( - (_) => - new Desk({ - ..._, - id: _.id || `desk-${randomInt(999_999)}`, - }), - ), + list.map((_) => ({ + ..._, + id: _.id || `desk-${randomInt(999_999)}`, + })), ); } catch (e) { console.error(e); diff --git a/apps/concierge/src/app/desks/desks-state.service.ts b/apps/concierge/src/app/desks/desks-state.service.ts index 8ee8d5ab361..e7cd3319ae6 100644 --- a/apps/concierge/src/app/desks/desks-state.service.ts +++ b/apps/concierge/src/app/desks/desks-state.service.ts @@ -12,6 +12,7 @@ import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { deleteDeskAsset, deskFromAsset, + findDeskByLegacyId, queryDeskAssets, queryDeskAssetsForZones, queryDeskAssetsInZone, @@ -509,14 +510,18 @@ export class DesksStateService extends AsyncHandler { downloadFile('desks.csv', jsonToCsv(rows)); } - public async addDesks(list: Desk[]) { + /** + * Save new or imported desks. Pass imported CSV rows as plain objects, not + * `Desk` instances, so only the row's own columns update an existing desk. + */ + public async addDesks(list: Partial[]) { const selected_zones = this._getSelectedZones(); const all_zones = this._filters().zones?.includes('All'); const fallback_zone = !all_zones && selected_zones.length ? selected_zones[0] : ''; // Group the desks by their level so each level's metadata is only // written with its own desks. - const groups = new Map(); + const groups = new Map[]>(); for (const desk of list) { // imported rows carry the level as `zone_id` const zone = desk.zone?.id || desk['zone_id'] || fallback_zone; @@ -544,7 +549,8 @@ export class DesksStateService extends AsyncHandler { continue; } const desk_list = await this._storedDeskList(zone); - for (const desk of desks) { + for (const row of desks) { + const desk = row instanceof Desk ? row : new Desk(row); const idx = desk_list.findIndex((_) => _.id === desk.id); if (idx >= 0) desk_list[idx] = desk; else desk_list.push(desk); @@ -559,20 +565,18 @@ export class DesksStateService extends AsyncHandler { } /** - * Desks without an asset type (imported rows, metadata era exports) update - * the desk asset with the same id or map id rather than creating a duplicate. - * Migrated desk assets keep the original desk id as their map id. + * Imported rows update the desk asset with the same id or map id rather + * than creating a duplicate. Migrated desk assets keep the original desk + * id as their map id. Columns missing from the row keep the asset's values. */ - private _matchDeskAsset(desk: Desk, existing: Desk[]): Desk { - if (desk['asset_type_id']) return desk; - const map_id = desk.map_id || desk.id; - const match = existing.find( - (asset) => asset.id === desk.id || asset.map_id === map_id, - ); - if (!match) return desk; + private _matchDeskAsset(row: Partial, existing: Desk[]): Desk { + const match = + findDeskByLegacyId(existing, row.id) || + findDeskByLegacyId(existing, row.map_id); + if (!match) return new Desk(row); return new Desk({ ...match, - ...desk, + ...row, id: match.id, map_id: match.map_id, asset_type_id: match['asset_type_id'], diff --git a/apps/concierge/src/app/desks/desks-topbar.component.ts b/apps/concierge/src/app/desks/desks-topbar.component.ts index 2065ea15060..48a546a9c12 100644 --- a/apps/concierge/src/app/desks/desks-topbar.component.ts +++ b/apps/concierge/src/app/desks/desks-topbar.component.ts @@ -298,13 +298,10 @@ export class DesksTopbarComponent extends AsyncHandler implements OnInit { try { const list = csvToJson(data) || []; this._desks.addDesks( - list.map( - (_) => - new Desk({ - ..._, - id: _.id || `desk-${randomInt(999_999)}`, - }), - ), + list.map((_) => ({ + ..._, + id: _.id || `desk-${randomInt(999_999)}`, + })), ); } catch (e) { console.error(e); diff --git a/apps/concierge/src/app/desks/desks.component.ts b/apps/concierge/src/app/desks/desks.component.ts index 0bd301aaab6..eed6b2e0003 100644 --- a/apps/concierge/src/app/desks/desks.component.ts +++ b/apps/concierge/src/app/desks/desks.component.ts @@ -20,7 +20,6 @@ import { AsyncHandler, BuildingLevel, csvToJson, - Desk, downloadFile, jsonToCsv, loadTextFileFromInputEvent, @@ -533,13 +532,10 @@ export class DesksComponent extends AsyncHandler implements OnInit, OnDestroy { try { const list = csvToJson(data) || []; this._state.addDesks( - list.map( - (_) => - new Desk({ - ..._, - id: _.id || `desk-${randomInt(999_999)}`, - }), - ), + list.map((_) => ({ + ..._, + id: _.id || `desk-${randomInt(999_999)}`, + })), ); } catch (e) { console.error(e); diff --git a/apps/concierge/src/tests/desks/desks-state.service.spec.ts b/apps/concierge/src/tests/desks/desks-state.service.spec.ts index 94ff3b37637..932ad8f23ab 100644 --- a/apps/concierge/src/tests/desks/desks-state.service.spec.ts +++ b/apps/concierge/src/tests/desks/desks-state.service.spec.ts @@ -288,6 +288,8 @@ describe('DesksStateService', () => { zone_id: 'level-2', identifier: 'Old Name', map_id: 'desk-6.06.1', + bookable: true, + images: ['desk.png'], other_data: { floorsense_id: 'fs-1' }, }, ] @@ -300,12 +302,8 @@ describe('DesksStateService', () => { // a metadata era CSV row, identified by the original desk id await spectator.service.addDesks([ - new Desk({ - id: 'desk-6.06.1', - name: 'New Name', - zone_id: 'level-2', - } as any), - new Desk({ id: 'desk-new', name: 'Brand New' }), + { id: 'desk-6.06.1', name: 'New Name', zone_id: 'level-2' } as any, + { id: 'desk-new', name: 'Brand New' }, ]); expect(ts_client_mod.updateAsset).toHaveBeenCalledTimes(1); @@ -315,6 +313,9 @@ describe('DesksStateService', () => { zone_id: 'level-2', identifier: 'New Name', map_id: 'desk-6.06.1', + // columns missing from the row keep the asset's values + bookable: true, + images: ['desk.png'], other_data: expect.objectContaining({ floorsense_id: 'fs-1' }), }), ); diff --git a/apps/workplace/src/app/book/code-flow.component.ts b/apps/workplace/src/app/book/code-flow.component.ts index bcc271f85cf..7e0dbe851e2 100644 --- a/apps/workplace/src/app/book/code-flow.component.ts +++ b/apps/workplace/src/app/book/code-flow.component.ts @@ -10,7 +10,11 @@ import { viewChild, } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { queryDeskAssetsInZone } from '@placeos/assets'; +import { + deskFromAsset, + findDeskByLegacyId, + queryDeskAssetsInZone, +} from '@placeos/assets'; import { BookingFormService, queryBookings, @@ -345,19 +349,15 @@ export class BookCodeFlowComponent /** * Codes printed before desks moved to assets hold the original desk id, * which desk assets keep as their map id. Bookings reference the asset id. + * The booked desk can be in any building, so search the whole org. */ private async _deskAssetId(code: string): Promise { if (!this._settings.get('app.desks.use_assets')) return code; - const building_id = this._org.building?.id; - if (!building_id) return code; - const assets = await queryDeskAssetsInZone(building_id).catch(() => []); - const asset = assets.find( - (item) => - item.id === code || - item.map_id === code || - item.other_data?.['map_id'] === code, - ); - return asset?.id || code; + const org_id = this._org.organisation?.id; + if (!org_id) return code; + const assets = await queryDeskAssetsInZone(org_id).catch(() => []); + const desks = assets.map((asset) => deskFromAsset(asset)); + return findDeskByLegacyId(desks, code)?.id || code; } private async _checkinEvent(space_id: string, email?: string) { diff --git a/apps/workplace/src/app/book/desk-flow-new/desk-flow.component.ts b/apps/workplace/src/app/book/desk-flow-new/desk-flow.component.ts index 85c6b1f525a..49c0b1752c9 100644 --- a/apps/workplace/src/app/book/desk-flow-new/desk-flow.component.ts +++ b/apps/workplace/src/app/book/desk-flow-new/desk-flow.component.ts @@ -9,7 +9,11 @@ import { } from '@angular/core'; import { MatRippleModule } from '@angular/material/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { deskFromAsset, queryDeskAssetsInZone } from '@placeos/assets'; +import { + deskFromAsset, + findDeskByLegacyId, + queryDeskAssetsInZone, +} from '@placeos/assets'; import { BookingAsset, BookingFlowView, @@ -42,14 +46,6 @@ import { import { DeskFlowSelectComponent } from './desk-flow-select.component'; import { NewDeskFlowSuccessComponent } from './desk-flow-success.component'; -/** Desk links use the desk asset id, or the original desk id kept as the map id */ -function deskMatchesLink( - desk: { id: string; map_id?: string }, - asset_id: string, -) { - return desk.id === asset_id || desk.map_id === asset_id; -} - @Component({ selector: 'desk-flow-new', template: ` @@ -335,9 +331,7 @@ export class DeskFlowNewComponent extends AsyncHandler implements OnInit { const deadline = Date.now() + wait_ms; do { const resources = await this._booking_form.listResources(); - const resource = resources.find((item) => - deskMatchesLink(item, asset_id), - ); + const resource = findDeskByLegacyId(resources, asset_id); if (resource) return resource; if (Date.now() >= deadline) break; await new Promise((resolve) => setTimeout(resolve, 50)); @@ -373,25 +367,25 @@ export class DeskFlowNewComponent extends AsyncHandler implements OnInit { return null; } + /** + * Desk assets list the org in their `zones`, so one query covers every + * building. The desk's building may be in a region that is not loaded yet. + */ private async _findDeskAssetFromBuildings( asset_id: string, ): Promise { - for (const building of this._org.buildings || []) { - const levels = this._org.levelsForBuilding(building); - const assets = await queryDeskAssetsInZone(building.id).catch( - () => [], - ); - const desk = assets - .map((asset) => - deskFromAsset( - asset, - levels.find((level) => level.id === asset.zone_id), - ), - ) - .find((item) => deskMatchesLink(item, asset_id)); - if (desk) return desk as unknown as BookingAsset; - } - return null; + const assets = await queryDeskAssetsInZone( + this._org.organisation?.id, + ).catch(() => []); + const desks = assets.map((asset) => deskFromAsset(asset)); + const asset = + assets[desks.indexOf(findDeskByLegacyId(desks, asset_id))]; + if (!asset) return null; + await this._org.loadBuildingsForZones([asset.zones || []]); + const level = (this._org.levels || []).find( + ({ id }) => id === asset.zone_id, + ); + return deskFromAsset(asset, level) as unknown as BookingAsset; } private async _initNearbyDeskBooking(space_id: string, event_date: number) { diff --git a/apps/workplace/src/tests/book/code-flow.component.spec.ts b/apps/workplace/src/tests/book/code-flow.component.spec.ts index b77fa33d51e..623d1d375f8 100644 --- a/apps/workplace/src/tests/book/code-flow.component.spec.ts +++ b/apps/workplace/src/tests/book/code-flow.component.spec.ts @@ -68,9 +68,9 @@ describe('BookCodeFlowComponent', () => { ); vi.spyOn( spectator.inject(OrganisationService), - 'building', + 'organisation', 'get', - ).mockReturnValue({ id: 'bld-1' } as any); + ).mockReturnValue({ id: 'org-1' } as any); const page = (data: any[]) => Promise.resolve({ data, total: data.length, next: () => null }); vi.mocked(ts_client.queryAssetCategories).mockReturnValue( @@ -79,11 +79,11 @@ describe('BookCodeFlowComponent', () => { vi.mocked(ts_client.queryAssetTypes).mockReturnValue( page([{ id: 'type-1', name: '_DESKS_' }]) as any, ); - // desk assets are found by building + // the booked desk can be in any building, so search the org vi.mocked(ts_client.queryAssets).mockImplementation( (query: any) => page( - query.zones === 'bld-1' + query.zones === 'org-1' ? [ { id: 'asset-1', diff --git a/apps/workplace/src/tests/book/desk-flow-new.component.spec.ts b/apps/workplace/src/tests/book/desk-flow-new.component.spec.ts index 69abf8ce79a..5ef8bde1d0e 100644 --- a/apps/workplace/src/tests/book/desk-flow-new.component.spec.ts +++ b/apps/workplace/src/tests/book/desk-flow-new.component.spec.ts @@ -183,6 +183,15 @@ describe('DeskFlowNewComponent', () => { }, ]), levelsForRegion: vi.fn(() => []), + loadBuildingsForZones: vi.fn(async () => []), + organisation: { id: 'org-1' }, + levels: [ + { + id: 'level-2', + parent_id: 'building-2', + tags: [], + }, + ], find: vi.fn((id: string) => ['building-1', 'building-2'].includes(id) ? { id } @@ -324,12 +333,14 @@ describe('DeskFlowNewComponent', () => { vi.mocked(queryAssets).mockImplementation( (query: any) => page( - query.zones === 'building-2' + // desk assets list the org in their zones + query.zones === 'org-1' ? [ { id: 'asset-456', asset_type_id: 'type-1', zone_id: 'level-2', + zones: ['org-1', 'building-2', 'level-2'], identifier: 'Desk 456', map_id: 'desk-456', }, @@ -351,6 +362,10 @@ describe('DeskFlowNewComponent', () => { expect(model().resources).toEqual([ expect.objectContaining({ id: 'asset-456', name: 'Desk 456' }), ]); + // the desk's building may be in a region that is not loaded yet + expect( + spectator.inject(OrganisationService).loadBuildingsForZones, + ).toHaveBeenCalledWith([['org-1', 'building-2', 'level-2']]); }); it('should select a nearby desk by resource id when no map id is available', async () => { diff --git a/libs/assets/src/lib/assets.fn.ts b/libs/assets/src/lib/assets.fn.ts index 9fe54e3a3a0..8715eb6b2f9 100644 --- a/libs/assets/src/lib/assets.fn.ts +++ b/libs/assets/src/lib/assets.fn.ts @@ -127,15 +127,24 @@ export function saveAssetCategory(category: Partial) { const _GROUPS_CACHE = new Map(); const REMOVE_QUERY_KEYS = ['period_start', 'period_end', 'type', 'rejected']; -/** Query assets, following pagination until every page is loaded */ -export async function queryAllAssetPages(query: any = {}) { +/** Upper bound on pages loaded by `queryAllAssetPages` (100 × 500 assets) */ +const MAX_ASSET_PAGES = 100; + +/** + * Query assets, following pagination until every page is loaded. + * `zones` is a comma separated list, assets must be in every zone listed. + */ +export async function queryAllAssetPages( + query: Parameters[0] & { zones?: string } = {}, +) { let response = await queryAssetsAPI({ ...query, limit: query.limit || 500, }); let total = response.total; const data: PlaceAsset[] = [...response.data]; - while (typeof response.next === 'function') { + for (let page = 1; page < MAX_ASSET_PAGES; page++) { + if (typeof response.next !== 'function') break; const next = response.next(); if (!next) break; response = await next; diff --git a/libs/assets/src/lib/desk-assets.fn.spec.ts b/libs/assets/src/lib/desk-assets.fn.spec.ts index 332db4515bc..f3ef6f0fbbc 100644 --- a/libs/assets/src/lib/desk-assets.fn.spec.ts +++ b/libs/assets/src/lib/desk-assets.fn.spec.ts @@ -231,6 +231,19 @@ describe('[Desk Assets]', () => { expect(desks.deskFromAsset(asset).map_id).toBe('desk-legacy'); }); + it('should find desks by asset id before the original desk id', async () => { + const { findDeskByLegacyId } = await load_module(); + const desks = [ + { id: 'asset-1', map_id: 'desk-2' }, + { id: 'desk-2', map_id: 'desk-9' }, + ]; + + expect(findDeskByLegacyId(desks, 'desk-2')).toBe(desks[1]); + expect(findDeskByLegacyId(desks, 'desk-9')).toBe(desks[1]); + expect(findDeskByLegacyId(desks, 'missing')).toBeUndefined(); + expect(findDeskByLegacyId(desks, '')).toBeUndefined(); + }); + it('should save and delete desk assets', async () => { const desks = await load_module(); vi.mocked(ts_client.queryAssetCategories).mockReturnValue( diff --git a/libs/assets/src/lib/desk-assets.fn.ts b/libs/assets/src/lib/desk-assets.fn.ts index 6735b42e572..e95c13cd55e 100644 --- a/libs/assets/src/lib/desk-assets.fn.ts +++ b/libs/assets/src/lib/desk-assets.fn.ts @@ -86,6 +86,8 @@ export async function queryDeskAssets(zone_id: string): Promise { export async function queryDeskAssetsInZone( zone_id: string, ): Promise { + // an empty `zones` filter is not a scope, e.g. the building is not loaded + if (!zone_id) return []; const type_id = await resolveDeskTypeId(); const response = await queryAllAssetPages({ zones: zone_id, type_id }); return response.data; @@ -107,6 +109,22 @@ export async function queryDeskAssetsForZones( return flatten(results); } +/** + * Find the desk for an id from a link, check-in code, favourite or import. + * Matches the desk asset id first, then the map id, which migrated desk assets + * keep as the original metadata desk id. + */ +export function findDeskByLegacyId( + desks: T[], + id: string | undefined, +): T | undefined { + if (!id) return undefined; + return ( + desks.find((desk) => desk.id === id) || + desks.find((desk) => desk.map_id === id) + ); +} + /** Fields with a dedicated place on the asset or desk, everything else is kept in `other_data` */ const DESK_ASSET_KEYS = new Set([ 'id', diff --git a/libs/assets/src/lib/locker-assets.fn.ts b/libs/assets/src/lib/locker-assets.fn.ts index 0f8e8dba206..ca848aceca9 100644 --- a/libs/assets/src/lib/locker-assets.fn.ts +++ b/libs/assets/src/lib/locker-assets.fn.ts @@ -134,6 +134,8 @@ export async function queryLockerBankAssetsForZones( export async function queryLockerBankAssetsInZone( zone_id: string, ): Promise { + // an empty `zones` filter is not a scope, e.g. the building is not loaded + if (!zone_id) return []; const type_id = await resolveLockerBankTypeId(); const response = await queryAllAssetPages({ zones: zone_id, type_id }); return response.data; @@ -175,6 +177,8 @@ export async function queryLockerAssetsForZones( export async function queryLockerAssetsInZone( zone_id: string, ): Promise { + // an empty `zones` filter is not a scope, e.g. the building is not loaded + if (!zone_id) return []; const type_id = await resolveLockerTypeId(); const response = await queryAllAssetPages({ zones: zone_id, type_id }); return response.data; diff --git a/libs/assets/src/lib/parking-assets.fn.ts b/libs/assets/src/lib/parking-assets.fn.ts index 644236290c2..a245f6d28bc 100644 --- a/libs/assets/src/lib/parking-assets.fn.ts +++ b/libs/assets/src/lib/parking-assets.fn.ts @@ -208,6 +208,8 @@ export async function queryParkingSpacesForZones( export async function queryParkingSpacesInZone( zone_id: string, ): Promise { + // an empty `zones` filter is not a scope, e.g. the building is not loaded + if (!zone_id) return []; const type_id = await resolveParkingTypeId(); const response = await queryAllAssetPages({ zones: zone_id, type_id }); return response.data; diff --git a/libs/bookings/src/lib/booking-form.service.ts b/libs/bookings/src/lib/booking-form.service.ts index e9a001e0949..221196a84dd 100644 --- a/libs/bookings/src/lib/booking-form.service.ts +++ b/libs/bookings/src/lib/booking-form.service.ts @@ -111,6 +111,7 @@ import { DeskQuestionsModalComponent } from './desk-questions-modal.component'; import { AssetStateService } from 'libs/assets/src/lib/asset-state.service'; import { validateAssetRequestsForResource } from 'libs/assets/src/lib/assets.fn'; +import { findDeskByLegacyId } from 'libs/assets/src/lib/desk-assets.fn'; import { openConfirmModal } from 'libs/components/src/lib/confirm-modal.component'; import { PaymentsService } from 'libs/payments/src/lib/payments.service'; @@ -2682,16 +2683,8 @@ export class BookingFormService extends AsyncHandler { const favourites = this._settings.get(SETTING_KEYS.FAVORITE_DESKS) || []; if (!favourites.length) return; - const ids = new Set(desks.map((desk) => desk.id)); - const asset_ids = new Map( - desks - .filter((desk) => desk.map_id && desk.map_id !== desk.id) - .map((desk) => [desk.map_id, desk.id]), - ); const updated = unique( - favourites.map((id) => - ids.has(id) ? id : asset_ids.get(id) || id, - ), + favourites.map((id) => findDeskByLegacyId(desks, id)?.id || id), ); if (updated.join() === favourites.join()) return; this._settings.saveUserSetting(SETTING_KEYS.FAVORITE_DESKS, updated); diff --git a/libs/mocks/src/lib/api/assets.data.ts b/libs/mocks/src/lib/api/assets.data.ts index c7d094f168a..dedfcebd5fa 100644 --- a/libs/mocks/src/lib/api/assets.data.ts +++ b/libs/mocks/src/lib/api/assets.data.ts @@ -1,6 +1,7 @@ import { padString, predictableRandomInt } from '@placeos/common'; import { getUnixTime, subDays, subMonths } from 'date-fns'; import { MOCK_STAFF } from './users.data'; +import { MOCK_BUILDINGS, MOCK_LEVELS, MOCK_REGIONS } from './zone.data'; export const MOCK_CATEGORIES = [ { @@ -636,8 +637,19 @@ export const getAssetsRequiringMaintenance = () => const MOCK_PARKING_ASSETS_CACHE: Record = {}; +/** Every zone a mock level is in: org, region, building and level */ +export function mockLevelZones(level_id: string): string[] { + const level = MOCK_LEVELS.find((_) => _.id === level_id); + const building = MOCK_BUILDINGS.find((_) => _.id === level?.parent_id); + const region = MOCK_REGIONS.find((_) => _.id === building?.parent_id); + return [region?.parent_id, region?.id, building?.id, level_id].filter( + Boolean, + ); +} + export function generateMockParkingAssets(zone_id: string) { if (!MOCK_PARKING_ASSETS_CACHE[zone_id]) { + const zones = mockLevelZones(zone_id); const parts = zone_id.split('-'); const id = parts[parts.length - 1]; MOCK_PARKING_ASSETS_CACHE[zone_id] = new Array(18 * 6) @@ -660,6 +672,7 @@ export function generateMockParkingAssets(zone_id: string) { assigned_name: assignee.name || '', asset_type_id: '_parking_type_', zone_id, + zones, notes: '', place_groups: [], features: [], diff --git a/libs/mocks/src/lib/api/assets.mock.ts b/libs/mocks/src/lib/api/assets.mock.ts index 7ea55800275..9f7d34b7630 100644 --- a/libs/mocks/src/lib/api/assets.mock.ts +++ b/libs/mocks/src/lib/api/assets.mock.ts @@ -7,7 +7,9 @@ import { MOCK_CATEGORIES, MOCK_PRODUCTS, MOCK_PURCHASE_ORDERS, + mockLevelZones, } from './assets.data'; +import { MOCK_LEVELS } from './zone.data'; const BASE_PATH = '/api/engine/v2'; @@ -239,9 +241,22 @@ export function registerMockAssets() { callback: (req) => { const type_id = req.query_params?.type_id; const zone_id = req.query_params?.zone_id; + // `zones` is comma separated, the asset must be in every one + const zones = `${req.query_params?.zones || ''}` + .split(',') + .filter(Boolean); if (type_id === '_parking_type_' && zone_id) { return generateMockParkingAssets(zone_id); } + if (type_id === '_parking_type_' && zones.length) { + MOCK_LEVELS.filter( + (level) => + level.type === 'parking' && + zones.every((id) => + mockLevelZones(level.id).includes(id), + ), + ).forEach((level) => generateMockParkingAssets(level.id)); + } let results = [...MOCK_ASSETS, ...getAllMockParkingAssets()]; if (type_id) { results = results.filter((a) => a.asset_type_id === type_id); @@ -253,6 +268,11 @@ export function registerMockAssets() { (a.zones || []).includes(zone_id), ); } + if (zones.length) { + results = results.filter((a) => + zones.every((id) => (a.zones || []).includes(id)), + ); + } return results; }, });