From 8cce11b8fef81ab260f5a22131d762de9276ce39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toma=CC=81s=20Du=CC=88ringer?= Date: Tue, 17 Feb 2026 13:35:43 -0300 Subject: [PATCH 1/2] Importing reports from Spectora into Fixle --- src/client.ts | 47 ++++++++++++++++++++++++++++++++------------ tests/client.test.ts | 18 +++++++++-------- 2 files changed, 44 insertions(+), 21 deletions(-) diff --git a/src/client.ts b/src/client.ts index 5e52883..0ebac90 100644 --- a/src/client.ts +++ b/src/client.ts @@ -37,7 +37,7 @@ export interface Appliance { */ export interface PropertyApplianceRequest { property_appliance: { - inspection_id?: number | null; + inspection_id?: string | null; serial: string | null; model: string | null; year: string | null; @@ -133,7 +133,7 @@ interface JsonApiErrorResponse { /** * Client for interacting with the Fixle API - * + * * @example * const client = new FixleClient({ * apiUrl: 'https://api.fixle.com', @@ -144,7 +144,7 @@ interface JsonApiErrorResponse { * const propertyId = await client.findOrCreateProperty('123 Main St, Portland, OR 97201'); * * // Create an inspection - * await client.createInspection(propertyId, 12345); + * await client.createInspection(propertyId, 'inspection-slug-123'); * * // Add an appliance * await client.createAppliance(propertyId, { @@ -257,7 +257,7 @@ export class FixleClient { * { email: 'buyer@example.com', name: 'John Doe' } * ]); */ - async findOrCreateProperty(address: string, buyers?: Contact[]): Promise { + async findOrCreateProperty(address: string, buyers?: Contact[]): Promise { const parts = address.split(',').map(s => s.trim()); const streetAddress = parts[0] || address; const cityStateZip = parts[1] || ''; @@ -276,7 +276,7 @@ export class FixleClient { }; const response = await this.makeApiRequest('POST', '/api/v1/properties', propertyData); - return parseInt(response.data?.id || response.id); + return response.data?.id || response.id; } /** @@ -289,22 +289,22 @@ export class FixleClient { * @throws Error if the API request fails or the property doesn't exist * * @example - * const fixleInspectionId = await client.createInspection(123, 45678); + * const fixleInspectionId = await client.createInspection('6b1d7345-b8a5-4536-bc3d-a0dd72aafa75', 'ed4cbaf3-d0dc-4100-b970-0a1a22157e70'); * console.log(`Created inspection with Fixle ID: ${fixleInspectionId}`); * * @example * // With inspector info and sellers - * const fixleInspectionId = await client.createInspection(123, 45678, { + * const fixleInspectionId = await client.createInspection('6b1d7345-b8a5-4536-bc3d-a0dd72aafa75', 'ed4cbaf3-d0dc-4100-b970-0a1a22157e70', { * inspectorImageUrl: 'https://example.com/inspector.jpg', * inspectorName: 'John Inspector', * inspectorEmail: 'inspector@example.com', * sellers: [{ email: 'seller@example.com', name: 'Jane Seller' }] * }); */ - async createInspection(propertyId: number, externalInspectionId: number, options?: CreateInspectionOptions): Promise { + async createInspection(propertyId: string, externalInspectionId: string, options?: CreateInspectionOptions): Promise { const inspectionData: InspectionRequest = { inspection: { - external_id: externalInspectionId.toString(), + external_id: externalInspectionId, ...(options?.inspectorImageUrl && { inspector_image_url: options.inspectorImageUrl }), ...(options?.inspectorName && { inspector_name: options.inspectorName }), ...(options?.inspectorEmail && { inspector_email: options.inspectorEmail }), @@ -313,7 +313,7 @@ export class FixleClient { }; const response = await this.makeApiRequest('POST', `/api/v1/properties/${propertyId}/inspections`, inspectionData); - return parseInt(response.data?.id || response.id); + return response.data?.id || response.id; } /** @@ -329,7 +329,7 @@ export class FixleClient { * @throws Error if the API request fails or the property doesn't exist * * @example - * await client.createAppliance(123, { + * await client.createAppliance('6b1d7345-b8a5-4536-bc3d-a0dd72aafa75', { * item_name: 'Water Heater', * section_name: 'Basement', * brand: 'Rheem', @@ -339,7 +339,7 @@ export class FixleClient { * year: '2020' * }, 45678); */ - async createAppliance(propertyId: number, appliance: Appliance, inspectionId?: number): Promise { + async createAppliance(propertyId: string, appliance: Appliance, inspectionId?: string): Promise { const applianceData: PropertyApplianceRequest = { property_appliance: { ...(inspectionId !== undefined && { inspection_id: inspectionId }), @@ -354,6 +354,27 @@ export class FixleClient { await this.makeApiRequest('POST', `/api/v1/properties/${propertyId}/appliances`, applianceData); } + + /** + * Creates report records for an inspection + * + * Each report is associated with a single inspection + * + * @param reportExternalIds - External IDs of the reports to add to the inspection + * @param inspectionId - Fixle inspection ID to link the reports to + * @returns Promise that resolves when the reports are created + * @throws Error if the API request fails or the inspection doesn't exist + * + * @example + * await client.createReports(['75ba1b64-c25b-48a2-a86c-46865f8301bb'], '4f6f3501-a9d1-4139-b5ef-a0dd4f33c6e4'); + */ + async createReports(reportExternalIds: string[], inspectionId: string): Promise { + await this.makeApiRequest('POST', `/api/v1/reports/bulk_create`, { + inspection_id: inspectionId, + external_ids: reportExternalIds, + }); + } + /** * Triggers the appliance report generation for an inspection * @@ -373,7 +394,7 @@ export class FixleClient { * console.log(`Report status: ${result.status}, message: ${result.message}`); */ async triggerApplianceReport( - inspectionId: number, + inspectionId: string, reportId?: string, async: boolean = true ): Promise<{ status: string; message: string }> { diff --git a/tests/client.test.ts b/tests/client.test.ts index 2bfb585..fabe0ea 100644 --- a/tests/client.test.ts +++ b/tests/client.test.ts @@ -61,34 +61,36 @@ describe('FixleClient', () => { it('should create inspection without inspectorImageUrl and return Fixle ID', async () => { setupHttpMock('{"data":{"id":"456"}}'); - const fixleInspectionId = await client.createInspection(123, 45678); + const fixleInspectionId = await client.createInspection(123, 'slug-45678'); expect(fixleInspectionId).toBe(456); const parsedBody = JSON.parse(capturedBody); - expect(parsedBody.inspection.external_id).toBe('45678'); + expect(parsedBody.inspection.external_id).toBe('slug-45678'); expect(parsedBody.inspection.inspector_image_url).toBeUndefined(); }); it('should create inspection with inspectorImageUrl and return Fixle ID', async () => { setupHttpMock('{"data":{"id":"456"}}'); - const fixleInspectionId = await client.createInspection(123, 45678, 'https://example.com/inspector.jpg'); + const fixleInspectionId = await client.createInspection(123, 'slug-45678', { + inspectorImageUrl: 'https://example.com/inspector.jpg', + }); expect(fixleInspectionId).toBe(456); const parsedBody = JSON.parse(capturedBody); - expect(parsedBody.inspection.external_id).toBe('45678'); + expect(parsedBody.inspection.external_id).toBe('slug-45678'); expect(parsedBody.inspection.inspector_image_url).toBe('https://example.com/inspector.jpg'); }); - it('should include empty string inspectorImageUrl when explicitly provided', async () => { + it('should not include inspectorImageUrl when not provided in options', async () => { setupHttpMock('{"data":{"id":"456"}}'); - const fixleInspectionId = await client.createInspection(123, 45678, ''); + const fixleInspectionId = await client.createInspection(123, 'slug-45678', {}); expect(fixleInspectionId).toBe(456); const parsedBody = JSON.parse(capturedBody); - expect(parsedBody.inspection.external_id).toBe('45678'); - expect(parsedBody.inspection.inspector_image_url).toBe(''); + expect(parsedBody.inspection.external_id).toBe('slug-45678'); + expect(parsedBody.inspection.inspector_image_url).toBeUndefined(); }); }); From 8a74957fa7ddb82c6d886d647d2a52913e5e12bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toma=CC=81s=20Du=CC=88ringer?= Date: Tue, 17 Feb 2026 13:40:16 -0300 Subject: [PATCH 2/2] Fixing tests --- tests/client.test.ts | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/client.test.ts b/tests/client.test.ts index fabe0ea..0275c11 100644 --- a/tests/client.test.ts +++ b/tests/client.test.ts @@ -53,7 +53,7 @@ describe('FixleClient', () => { setupHttpMock('{"data":{"id":"123"}}'); const propertyId = await client.findOrCreateProperty('123 Main St, Portland, OR 97201'); - expect(propertyId).toBe(123); + expect(propertyId).toBe('123'); }); }); @@ -61,9 +61,9 @@ describe('FixleClient', () => { it('should create inspection without inspectorImageUrl and return Fixle ID', async () => { setupHttpMock('{"data":{"id":"456"}}'); - const fixleInspectionId = await client.createInspection(123, 'slug-45678'); + const fixleInspectionId = await client.createInspection('123', 'slug-45678'); - expect(fixleInspectionId).toBe(456); + expect(fixleInspectionId).toBe('456'); const parsedBody = JSON.parse(capturedBody); expect(parsedBody.inspection.external_id).toBe('slug-45678'); expect(parsedBody.inspection.inspector_image_url).toBeUndefined(); @@ -72,11 +72,11 @@ describe('FixleClient', () => { it('should create inspection with inspectorImageUrl and return Fixle ID', async () => { setupHttpMock('{"data":{"id":"456"}}'); - const fixleInspectionId = await client.createInspection(123, 'slug-45678', { + const fixleInspectionId = await client.createInspection('123', 'slug-45678', { inspectorImageUrl: 'https://example.com/inspector.jpg', }); - expect(fixleInspectionId).toBe(456); + expect(fixleInspectionId).toBe('456'); const parsedBody = JSON.parse(capturedBody); expect(parsedBody.inspection.external_id).toBe('slug-45678'); expect(parsedBody.inspection.inspector_image_url).toBe('https://example.com/inspector.jpg'); @@ -85,9 +85,9 @@ describe('FixleClient', () => { it('should not include inspectorImageUrl when not provided in options', async () => { setupHttpMock('{"data":{"id":"456"}}'); - const fixleInspectionId = await client.createInspection(123, 'slug-45678', {}); + const fixleInspectionId = await client.createInspection('123', 'slug-45678', {}); - expect(fixleInspectionId).toBe(456); + expect(fixleInspectionId).toBe('456'); const parsedBody = JSON.parse(capturedBody); expect(parsedBody.inspection.external_id).toBe('slug-45678'); expect(parsedBody.inspection.inspector_image_url).toBeUndefined(); @@ -108,7 +108,7 @@ describe('FixleClient', () => { it('should create appliance without inspectionId', async () => { setupHttpMock('{"data":{"id":"789"}}'); - await client.createAppliance(123, testAppliance); + await client.createAppliance('123', testAppliance); const parsedBody = JSON.parse(capturedBody); expect(parsedBody.property_appliance.model).toBe('XE50M06ST45U1'); @@ -121,18 +121,18 @@ describe('FixleClient', () => { it('should create appliance with inspectionId', async () => { setupHttpMock('{"data":{"id":"789"}}'); - await client.createAppliance(123, testAppliance, 45678); + await client.createAppliance('123', testAppliance, '45678'); const parsedBody = JSON.parse(capturedBody); expect(parsedBody.property_appliance.model).toBe('XE50M06ST45U1'); expect(parsedBody.property_appliance.serial).toBe('ABC123456'); - expect(parsedBody.property_appliance.inspection_id).toBe(45678); + expect(parsedBody.property_appliance.inspection_id).toBe('45678'); }); it('should include notes with item_name and brand', async () => { setupHttpMock('{"data":{"id":"789"}}'); - await client.createAppliance(123, testAppliance); + await client.createAppliance('123', testAppliance); const parsedBody = JSON.parse(capturedBody); expect(parsedBody.property_appliance.notes).toBe('Water Heater - Brand: Rheem'); @@ -141,7 +141,7 @@ describe('FixleClient', () => { it('should include location from section_name', async () => { setupHttpMock('{"data":{"id":"789"}}'); - await client.createAppliance(123, testAppliance); + await client.createAppliance('123', testAppliance); const parsedBody = JSON.parse(capturedBody); expect(parsedBody.property_appliance.location).toBe('Basement'); @@ -152,7 +152,7 @@ describe('FixleClient', () => { it('should trigger appliance report with default async=true', async () => { setupHttpMock('{"data":{"id":"456","attributes":{"status":"pending","message":"Report queued"}}}', 200); - const result = await client.triggerApplianceReport(456); + const result = await client.triggerApplianceReport('456'); expect(result.status).toBe('pending'); expect(result.message).toBe('Report queued'); @@ -161,7 +161,7 @@ describe('FixleClient', () => { it('should trigger appliance report with reportId', async () => { setupHttpMock('{"data":{"id":"456","attributes":{"status":"pending","message":"Report queued"}}}', 200); - const result = await client.triggerApplianceReport(456, 'report-123'); + const result = await client.triggerApplianceReport('456', 'report-123'); expect(result.status).toBe('pending'); expect(result.message).toBe('Report queued'); @@ -170,7 +170,7 @@ describe('FixleClient', () => { it('should trigger appliance report with async=false', async () => { setupHttpMock('{"data":{"id":"456","attributes":{"status":"completed","message":"Report generated"}}}', 200); - const result = await client.triggerApplianceReport(456, 'report-123', false); + const result = await client.triggerApplianceReport('456', 'report-123', false); expect(result.status).toBe('completed'); expect(result.message).toBe('Report generated'); @@ -179,7 +179,7 @@ describe('FixleClient', () => { it('should return default values when attributes are missing', async () => { setupHttpMock('{"data":{"id":"456"}}', 200); - const result = await client.triggerApplianceReport(456); + const result = await client.triggerApplianceReport('456'); expect(result.status).toBe('unknown'); expect(result.message).toBe('Report triggered');