From 7c54902a612470168f6332192458b6bb5dfc410e Mon Sep 17 00:00:00 2001 From: David Ly Date: Wed, 16 Oct 2024 00:40:24 +0200 Subject: [PATCH] Added post / delete / patch API routes to @monkvision/network --- .../hooks/usePhotoCaptureSightState.test.ts | 1 + packages/network/README.md | 53 ++++++ packages/network/src/api/api.ts | 7 +- .../network/src/api/inspection/mappers.ts | 38 +++-- .../network/src/api/inspection/requests.ts | 59 ++++++- packages/network/src/api/models/pricingV2.ts | 9 + packages/network/src/api/pricing/index.ts | 2 + packages/network/src/api/pricing/mappers.ts | 31 ++++ packages/network/src/api/pricing/requests.ts | 156 ++++++++++++++++++ packages/network/src/api/pricing/types.ts | 41 +++++ packages/network/src/api/react.ts | 27 +++ .../data/apiInspectionGet.data.json | 35 +++- .../inspection/data/apiInspectionGet.data.ts | 44 ++++- .../test/api/inspection/requests.test.ts | 69 +++++++- .../network/test/api/pricing/mappers.test.ts | 31 ++++ .../network/test/api/pricing/requests.test.ts | 136 +++++++++++++++ packages/network/test/api/react.test.ts | 30 ++++ packages/types/src/state/pricingV2.ts | 16 +- 18 files changed, 740 insertions(+), 45 deletions(-) create mode 100644 packages/network/src/api/pricing/index.ts create mode 100644 packages/network/src/api/pricing/mappers.ts create mode 100644 packages/network/src/api/pricing/requests.ts create mode 100644 packages/network/src/api/pricing/types.ts create mode 100644 packages/network/test/api/pricing/mappers.test.ts create mode 100644 packages/network/test/api/pricing/requests.test.ts diff --git a/packages/inspection-capture-web/test/PhotoCapture/hooks/usePhotoCaptureSightState.test.ts b/packages/inspection-capture-web/test/PhotoCapture/hooks/usePhotoCaptureSightState.test.ts index c0da54ed5..762caf340 100644 --- a/packages/inspection-capture-web/test/PhotoCapture/hooks/usePhotoCaptureSightState.test.ts +++ b/packages/inspection-capture-web/test/PhotoCapture/hooks/usePhotoCaptureSightState.test.ts @@ -76,6 +76,7 @@ function mockGetInspectionResponse( severityResults: [], vehicles: [], views: [], + pricings: [], }, }; takenSights.forEach((sight, index) => { diff --git a/packages/network/README.md b/packages/network/README.md index 71a6eaba8..46906a5e7 100644 --- a/packages/network/README.md +++ b/packages/network/README.md @@ -117,6 +117,59 @@ Update the vehicle of an inspection. |-----------|--------------------------------|-----------------------------|----------| | options | UpdateInspectionVehicleOptions | The options of the request. | ✔️ | +### createPricing +```typescript +import { MonkApi } from '@monkvision/network'; + +MonkApi.createPricing(options, apiConfig, dispatch); +``` + +Create a new pricing of an inspection. + +| Parameter | Type | Description | Required | +|-----------|----------------------|-----------------------------|----------| +| options | CreatePricingOptions | The options of the request. | ✔️ | + +### updatePricing +```typescript +import { MonkApi } from '@monkvision/network'; + +MonkApi.updatePricing(options, apiConfig, dispatch); +``` + +Update a pricing of an inspection. + +| Parameter | Type | Description | Required | +|-----------|----------------------|-----------------------------|----------| +| options | UpdatePricingOptions | The options of the request. | ✔️ | + +### deletePricing +```typescript +import { MonkApi } from '@monkvision/network'; + +MonkApi.deletePricing(options, apiConfig, dispatch); +``` + +Update a pricing of an inspection. + +| Parameter | Type | Description | Required | +|-----------|----------------------|-----------------------------|----------| +| options | DeletePricingOptions | The options of the request. | ✔️ | + +### updateAdditionalData +```typescript +import { MonkApi } from '@monkvision/network'; + +MonkApi.updateAdditionalData(options, apiConfig, dispatch); +``` + +Update the additional data of an inspection. + +| Parameter | Type | Description | Required | +|-----------|-----------------------------|-----------------------------|----------| +| options | UpdateAdditionalDataOptions | The options of the request. | ✔️ | + + # React Tools In order to simply integrate the Monk Api requests into your React app, you can make use of the `useMonkApi` hook. This custom hook returns a custom version of the `MonkApi` object described in the section above, in which the requests do diff --git a/packages/network/src/api/api.ts b/packages/network/src/api/api.ts index b4a968301..e24edca7a 100644 --- a/packages/network/src/api/api.ts +++ b/packages/network/src/api/api.ts @@ -1,8 +1,9 @@ -import { getInspection, createInspection } from './inspection'; +import { getInspection, createInspection, updateAdditionalData } from './inspection'; import { addImage } from './image'; import { startInspectionTasks, updateTaskStatus } from './task'; import { getLiveConfig } from './liveConfigs'; import { updateInspectionVehicle } from './vehicle'; +import { createPricing, deletePricing, updatePricing } from './pricing'; /** * Object regrouping the different API requests available to communicate with the API using the `@monkvision/network` @@ -16,4 +17,8 @@ export const MonkApi = { startInspectionTasks, getLiveConfig, updateInspectionVehicle, + updateAdditionalData, + createPricing, + deletePricing, + updatePricing, }; diff --git a/packages/network/src/api/inspection/mappers.ts b/packages/network/src/api/inspection/mappers.ts index cc1ca729a..c8a872093 100644 --- a/packages/network/src/api/inspection/mappers.ts +++ b/packages/network/src/api/inspection/mappers.ts @@ -15,7 +15,6 @@ import { MonkEntityType, Part, PricingV2, - PricingV2Details, PricingV2RelatedItemType, ProgressStatus, RenderedOutput, @@ -193,10 +192,12 @@ function mapParts(response: ApiInspectionGet): { parts: Part[]; partIds: string[ function mapPricingV2Details( apiPricingV2Details: ApiPricingV2Details | undefined, inspectionId: string, -): PricingV2Details { +): PricingV2 { const details = apiPricingV2Details as ApiPricingV2Details; return { inspectionId, + id: details.id, + entityType: MonkEntityType.PRICING, relatedItemType: details.related_item_type as PricingV2RelatedItemType, relatedItemId: details.related_item_id, pricing: details.pricing, @@ -205,22 +206,21 @@ function mapPricingV2Details( }; } -function mapPricingV2(response: ApiInspectionGet): PricingV2 | undefined { +function mapPricingV2(response: ApiInspectionGet): { + pricings: PricingV2[]; + pricingIds: string[]; +} { + const pricings: PricingV2[] = []; + const pricingIds: string[] = []; + if (!response.pricing) { - return undefined; + return { pricings, pricingIds }; } - return { - details: response?.pricing.details - ? Object.keys(response.pricing.details).reduce( - (prev, curr) => ({ - ...prev, - [curr]: mapPricingV2Details(response.pricing?.details[curr], response.id), - }), - {} as Record, - ) - : {}, - totalPrice: response.pricing.total_price, - }; + Object.values(response.pricing.details).forEach((details) => { + pricingIds.push(details.id); + pricings.push(mapPricingV2Details(details, response.id)); + }); + return { pricings, pricingIds }; } function mapSeverityResultRepairOperation( @@ -353,6 +353,7 @@ function mapInspection( severityResultIds: string[]; taskIds: string[]; vehicleId?: string; + pricingIds: string[]; }, ): Inspection { return { @@ -365,7 +366,7 @@ function mapInspection( vehicle: ids.vehicleId, wheelAnalysis: mapWheelAnalysis(response), severityResults: ids.severityResultIds, - pricing: mapPricingV2(response), + pricings: ids.pricingIds, additionalData: response.additional_data, }; } @@ -384,6 +385,7 @@ export function mapApiInspectionGet( const { parts, partIds } = mapParts(response); const { severityResults, severityResultIds } = mapSeverityResults(response); const { tasks, taskIds } = mapTasks(response); + const { pricings, pricingIds } = mapPricingV2(response); const vehicle = mapVehicle(response); const inspection = mapInspection(response, { imageIds, @@ -393,6 +395,7 @@ export function mapApiInspectionGet( partIds, severityResultIds, taskIds, + pricingIds, }); return { @@ -405,6 +408,7 @@ export function mapApiInspectionGet( tasks, vehicles: vehicle ? [vehicle] : [], views, + pricings, partOperations: [], }; } diff --git a/packages/network/src/api/inspection/requests.ts b/packages/network/src/api/inspection/requests.ts index 5c30d81cd..7461a3ebc 100644 --- a/packages/network/src/api/inspection/requests.ts +++ b/packages/network/src/api/inspection/requests.ts @@ -4,8 +4,9 @@ import { MonkActionType, MonkGotOneInspectionAction, MonkState, + MonkUpdatedOneInspectionAdditionalDataAction, } from '@monkvision/common'; -import { ComplianceOptions, CreateInspectionOptions } from '@monkvision/types'; +import { AdditionalData, ComplianceOptions, CreateInspectionOptions } from '@monkvision/types'; import { Dispatch } from 'react'; import { getDefaultOptions, MonkApiConfig } from '../config'; import { ApiIdColumn, ApiInspectionGet } from '../models'; @@ -86,3 +87,59 @@ export async function createInspection( body, }; } + +/** + * Options passed to the `updateAdditionalData` API request. + */ +export interface UpdateAdditionalDataOptions { + /** + * The ID of the inspection to update via the API. + */ + id: string; + /** + * Callback function that takes optional additional data and returns the updated additional data. + */ + callback: (additionalData?: AdditionalData) => AdditionalData; +} + +/** + * Update the additional data of inspection with the given options. + * See the `UpdateAdditionalDataOptions` interface for more details. + * + * @param options The options of the request. + * @param config The API config. + * @param [dispatch] Optional MonkState dispatch function that you can pass if you want this request to handle React + * state management for you. + * @see UpdateAdditionalDataOptions + */ +export async function updateAdditionalData( + options: UpdateAdditionalDataOptions, + config: MonkApiConfig, + dispatch?: Dispatch, +): Promise { + const { entities } = await getInspection({ id: options.id }, config); + const inspection = entities.inspections.find((i) => i.id === options.id); + if (!inspection) { + throw new Error('Inspection does not exist'); + } + const newAdditionalData = options.callback(inspection.additionalData); + + const kyOptions = getDefaultOptions(config); + const response = await ky.patch(`inspections/${options.id}`, { + ...kyOptions, + json: { additional_data: newAdditionalData }, + }); + const body = await response.json(); + dispatch?.({ + type: MonkActionType.UPDATED_ONE_INSPECTION_ADDITIONAL_DATA, + payload: { + inspectionId: options.id, + additionalData: newAdditionalData, + }, + }); + return { + id: body.id, + response, + body, + }; +} diff --git a/packages/network/src/api/models/pricingV2.ts b/packages/network/src/api/models/pricingV2.ts index d6594865d..b7fb248b1 100644 --- a/packages/network/src/api/models/pricingV2.ts +++ b/packages/network/src/api/models/pricingV2.ts @@ -1,3 +1,5 @@ +import { PricingV2RelatedItemType, VehiclePart } from '@monkvision/types'; + export type ApiRelatedItemType = 'part' | 'vehicle'; export type ApiRepairOperationsTypes = @@ -15,6 +17,7 @@ export type ApiRepairOperationsTypes = export type ApiHours = Record; export interface ApiPricingV2Details { + id: string; hours?: ApiHours; operations?: ApiRepairOperationsTypes[]; pricing?: number; @@ -28,3 +31,9 @@ export interface ApiPricingV2 { details: ApiDetails; total_price?: number; } + +export interface ApiPricingPost { + pricing: number; + related_item_type: PricingV2RelatedItemType; + part_type: VehiclePart | undefined; +} diff --git a/packages/network/src/api/pricing/index.ts b/packages/network/src/api/pricing/index.ts new file mode 100644 index 000000000..96f11f1f1 --- /dev/null +++ b/packages/network/src/api/pricing/index.ts @@ -0,0 +1,2 @@ +export * from './requests'; +export * from './types'; diff --git a/packages/network/src/api/pricing/mappers.ts b/packages/network/src/api/pricing/mappers.ts new file mode 100644 index 000000000..f2a48ce7e --- /dev/null +++ b/packages/network/src/api/pricing/mappers.ts @@ -0,0 +1,31 @@ +import { + MonkEntityType, + PricingV2, + PricingV2RelatedItemType, + RepairOperationType, + VehiclePart, +} from '@monkvision/types'; +import { ApiPricingPost, ApiPricingV2Details } from '../models'; +import { PricingOptions } from './types'; + +export function mapApiPricingPost(inspectionId: string, response: ApiPricingV2Details): PricingV2 { + return { + inspectionId, + id: response.id, + entityType: MonkEntityType.PRICING, + relatedItemType: response.related_item_type as PricingV2RelatedItemType, + relatedItemId: response.related_item_id, + pricing: response.pricing, + operations: response.operations as RepairOperationType[] | undefined, + hours: response.hours, + }; +} + +export function mapApiPricingPostRequest(options: PricingOptions): ApiPricingPost { + return { + pricing: options.pricing >= 0 ? options.pricing : 0, + related_item_type: options.type, + part_type: + options.type === PricingV2RelatedItemType.PART ? options.vehiclePart : VehiclePart.IGNORE, + }; +} diff --git a/packages/network/src/api/pricing/requests.ts b/packages/network/src/api/pricing/requests.ts new file mode 100644 index 000000000..36084b608 --- /dev/null +++ b/packages/network/src/api/pricing/requests.ts @@ -0,0 +1,156 @@ +import { Dispatch } from 'react'; +import { + MonkActionType, + MonkCreatedOnePricingAction, + MonkDeletedOnePricingAction, + MonkUpdatedOnePricingAction, +} from '@monkvision/common'; +import ky from 'ky'; +import { getDefaultOptions, MonkApiConfig } from '../config'; +import { MonkApiResponse } from '../types'; +import { ApiIdColumn, ApiPricingV2Details } from '../models'; +import { mapApiPricingPost, mapApiPricingPostRequest } from './mappers'; +import { PricingOptions } from './types'; + +/** + * Options passed to the `createPricing` API request. + */ +export interface CreatePricingOptions { + /** + * The ID of the inspection to update via the API. + */ + id: string; + /** + * Pricing used for the update operation. + */ + pricing: PricingOptions; +} + +/** + * Create a new pricing with the given options. See the `CreatePricingOptions` interface for more details. + * + * @param options The options of the inspection. + * @param config The API config. + * @param [dispatch] Optional MonkState dispatch function that you can pass if you want this request to handle React + * state management for you. + * @see CreatePricingOptions + */ + +export async function createPricing( + options: CreatePricingOptions, + config: MonkApiConfig, + dispatch?: Dispatch, +): Promise { + const kyOptions = getDefaultOptions(config); + const response = await ky.post(`inspections/${options.id}/pricing`, { + ...kyOptions, + json: mapApiPricingPostRequest(options.pricing), + }); + const body = await response.json(); + const pricing = mapApiPricingPost(options.id, body); + dispatch?.({ + type: MonkActionType.CREATED_ONE_PRICING, + payload: { pricing }, + }); + return { + id: body.id, + response, + body, + }; +} + +/** + * Options passed to the `deletePricing` API request. + */ +export interface DeletePricingOptions { + /** + * The ID of the inspection to update via the API. + */ + id: string; + /** + * Pricing ID that will be deleted. + */ + pricingId: string; +} + +/** + * Delete a pricing with the given options. See the `DeletePricingOptions` interface for more details. + * + * @param options The options of the inspection. + * @param config The API config. + * @param [dispatch] Optional MonkState dispatch function that you can pass if you want this request to handle React + * state management for you. + * @see DeletePricingOptions + */ + +export async function deletePricing( + options: DeletePricingOptions, + config: MonkApiConfig, + dispatch?: Dispatch, +): Promise { + const kyOptions = getDefaultOptions(config); + const response = await ky.delete(`inspections/${options.id}/pricing/${options.pricingId}`, { + ...kyOptions, + }); + const body = await response.json(); + dispatch?.({ + type: MonkActionType.DELETED_ONE_PRICING, + payload: { inspectionId: options.id, pricingId: body.id }, + }); + return { + id: body.id, + response, + body, + }; +} + +/** + * Options passed to the `updatePricing` API request. + */ +export interface UpdatePricingOptions { + /** + * The ID of the inspection to update via the API. + */ + id: string; + /** + * Pricing ID that will be update. + */ + pricingId: string; + /** + * The new price value. + */ + price: number; +} + +/** + * Update a pricing with the given options. See the `UpdatePricingOptions` interface for more details. + * + * @param options The options of the inspection. + * @param config The API config. + * @param [dispatch] Optional MonkState dispatch function that you can pass if you want this request to handle React + * state management for you. + * @see UpdatePricingOptions + */ + +export async function updatePricing( + options: UpdatePricingOptions, + config: MonkApiConfig, + dispatch?: Dispatch, +): Promise { + const kyOptions = getDefaultOptions(config); + const response = await ky.patch(`inspections/${options.id}/pricing/${options.pricingId}`, { + ...kyOptions, + json: { pricing: options.price }, + }); + const body = await response.json(); + const pricing = mapApiPricingPost(options.id, body); + dispatch?.({ + type: MonkActionType.UPDATED_ONE_PRICING, + payload: { pricing }, + }); + return { + id: body.id, + response, + body, + }; +} diff --git a/packages/network/src/api/pricing/types.ts b/packages/network/src/api/pricing/types.ts new file mode 100644 index 000000000..f11ab01d1 --- /dev/null +++ b/packages/network/src/api/pricing/types.ts @@ -0,0 +1,41 @@ +import { PricingV2RelatedItemType, VehiclePart } from '@monkvision/types'; + +/** + * Options for part-specific pricing. + */ +export interface PricingPartOptions { + /** + * The type of pricing, in this case for a part. + */ + type: PricingV2RelatedItemType.PART; + + /** + * The pricing value for the part. Must be a non-negative float. + */ + pricing: number; + + /** + * The specific vehicle part this pricing applies to. + */ + vehiclePart: VehiclePart; +} + +/** + * Options for car-wide pricing. + */ +export interface PricingVehicleOptions { + /** + * The type of pricing, in this case for the whole car. + */ + type: PricingV2RelatedItemType.VEHICLE; + + /** + * The pricing value for the entire car. + */ + pricing: number; +} + +/** + * Union type representing pricing options for either a car or a part. + */ +export type PricingOptions = PricingVehicleOptions | PricingPartOptions; diff --git a/packages/network/src/api/react.ts b/packages/network/src/api/react.ts index 9024ef5f8..a8bc474a3 100644 --- a/packages/network/src/api/react.ts +++ b/packages/network/src/api/react.ts @@ -111,5 +111,32 @@ export function useMonkApi(config: MonkApiConfig) { dispatch, handleError, ), + /** + * Update the additional data of an inspection. + * + * @param options The options of the request. + */ + updateAdditionalData: reactify(MonkApi.updateAdditionalData, config, dispatch, handleError), + /** + * Create a new pricing with the given options. See the `CreatePricingOptions` interface for more details. + * + * @param options The options of the inspection. + * @see CreatePricingOptions + */ + createPricing: reactify(MonkApi.createPricing, config, dispatch, handleError), + /** + * Delete a pricing with the given options. See the `DeletePricingOptions` interface for more details. + * + * @param options The options of the inspection. + * @see DeletePricingOptions + */ + deletePricing: reactify(MonkApi.deletePricing, config, dispatch, handleError), + /** + * Update a pricing with the given options. See the `UpdatePricingOptions` interface for more details. + * + * @param options The options of the inspection. + * @see UpdatePricingOptions + */ + updatePricing: reactify(MonkApi.updatePricing, config, dispatch, handleError), }; } diff --git a/packages/network/test/api/inspection/data/apiInspectionGet.data.json b/packages/network/test/api/inspection/data/apiInspectionGet.data.json index b41ba4538..d3a1ade48 100644 --- a/packages/network/test/api/inspection/data/apiInspectionGet.data.json +++ b/packages/network/test/api/inspection/data/apiInspectionGet.data.json @@ -3941,8 +3941,39 @@ ], "pdf_generation_ready": true, "pricing": { - "details": {}, - "total_price": 0 + "details": { + "bumper_back": { + "hours": { + "INGREDIENT": 2, + "PAINT": 2 + }, + "id": "8bbfcba0-fdee-4268-8bd5-69dffac87aea", + "inspection_id": "5843de96-21d3-282b-5829-7ce926f510a9", + "operations": ["painting"], + "pricing": 200, + "related_item_id": "fd6dd94c-d202-c9c4-fd07-7b33d524f14e", + "related_item_type": "part" + }, + "roof": { + "hours": null, + "id": "fed5af09-1e6d-0aac-febf-0d76194b3256", + "inspection_id": "5843de96-21d3-282b-5829-7ce926f510a9", + "operations": null, + "pricing": 200, + "related_item_id": "f78040b7-5562-55cf-f7ea-e2c852446d45", + "related_item_type": "part" + }, + "wheel_front_left": { + "hours": null, + "id": "b5d64933-4bce-9e9e-b5bc-eb4c4ce8a61c", + "inspection_id": "5843de96-21d3-282b-5829-7ce926f510a9", + "operations": null, + "pricing": 100, + "related_item_id": "41c29e52-6780-63e9-41a8-3c2d60a65b63", + "related_item_type": "part" + } + }, + "total_price": 500 }, "related_inspection_id": null, "severity_results": [ diff --git a/packages/network/test/api/inspection/data/apiInspectionGet.data.ts b/packages/network/test/api/inspection/data/apiInspectionGet.data.ts index 376381538..e55f972a2 100644 --- a/packages/network/test/api/inspection/data/apiInspectionGet.data.ts +++ b/packages/network/test/api/inspection/data/apiInspectionGet.data.ts @@ -262,10 +262,11 @@ export default { 'ca8b97ee-a50f-919b-cae1-3591a229bddc', '5dfffeae-6b5f-a699-5d95-5cd16c798ade', ], - pricing: { - details: {}, - totalPrice: 0, - }, + pricings: [ + '8bbfcba0-fdee-4268-8bd5-69dffac87aea', + 'fed5af09-1e6d-0aac-febf-0d76194b3256', + 'b5d64933-4bce-9e9e-b5bc-eb4c4ce8a61c', + ], additionalData: { damage_detection_version: 'v2', environment: { @@ -3516,5 +3517,40 @@ export default { renderedOutputs: ['662bc970-70d1-83de-6641-6b0f77f7af99'], }, ], + pricings: [ + { + entityType: 'PRICING', + hours: { + INGREDIENT: 2, + PAINT: 2, + }, + id: '8bbfcba0-fdee-4268-8bd5-69dffac87aea', + inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62', + operations: ['painting'], + pricing: 200, + relatedItemId: 'fd6dd94c-d202-c9c4-fd07-7b33d524f14e', + relatedItemType: 'part', + }, + { + entityType: 'PRICING', + hours: null, + id: 'fed5af09-1e6d-0aac-febf-0d76194b3256', + inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62', + operations: null, + pricing: 200, + relatedItemId: 'f78040b7-5562-55cf-f7ea-e2c852446d45', + relatedItemType: 'part', + }, + { + entityType: 'PRICING', + hours: null, + id: 'b5d64933-4bce-9e9e-b5bc-eb4c4ce8a61c', + inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62', + operations: null, + pricing: 100, + relatedItemId: '41c29e52-6780-63e9-41a8-3c2d60a65b63', + relatedItemType: 'part', + }, + ], partOperations: [], }; diff --git a/packages/network/test/api/inspection/requests.test.ts b/packages/network/test/api/inspection/requests.test.ts index 19d032077..d37237b61 100644 --- a/packages/network/test/api/inspection/requests.test.ts +++ b/packages/network/test/api/inspection/requests.test.ts @@ -1,16 +1,47 @@ +import { + AdditionalData, + ComplianceIssue, + ComplianceOptions, + Inspection, + MonkEntityType, + TaskName, +} from '@monkvision/types'; +import { MonkActionType } from '@monkvision/common'; +import ky from 'ky'; + +const additionalData = { + country: 'USA', + other_damages: [ + { + area: 'seats', + damage_type: 'scratch', + repair_cost: 544, + }, + ], +}; +const mockInspection: Inspection = { + id: 'test-inspection-id', + additionalData, + damages: [], + entityType: MonkEntityType.INSPECTION, + images: [], + parts: [], + tasks: [], +}; + jest.mock('../../../src/api/config', () => ({ getDefaultOptions: jest.fn(() => ({ prefixUrl: 'getDefaultOptionsTest' })), })); jest.mock('../../../src/api/inspection/mappers', () => ({ - mapApiInspectionGet: jest.fn(() => ({ test: 'hello' })), + mapApiInspectionGet: jest.fn(() => ({ + inspections: [mockInspection] as unknown as Inspection[], + parts: [], + })), mapApiInspectionPost: jest.fn(() => ({ test: 'ok-ok-ok' })), })); -import { ComplianceIssue, ComplianceOptions, TaskName } from '@monkvision/types'; -import ky from 'ky'; -import { MonkActionType } from '@monkvision/common'; import { getDefaultOptions } from '../../../src/api/config'; -import { createInspection, getInspection } from '../../../src/api/inspection'; +import { createInspection, getInspection, updateAdditionalData } from '../../../src/api/inspection'; import { mapApiInspectionGet, mapApiInspectionPost } from '../../../src/api/inspection/mappers'; const apiConfig = { @@ -74,4 +105,32 @@ describe('Inspection requests', () => { }); }); }); + + describe('updateAdditionalData request', () => { + it('should make the proper API call', async () => { + const id = 'test-inspection-id'; + const callback = (addData?: AdditionalData) => { + const newAddData = { + ...addData, + ...additionalData, + }; + return newAddData; + }; + const options = { id, callback }; + const result = await updateAdditionalData(options, apiConfig); + const response = await (ky.patch as jest.Mock).mock.results[0].value; + const body = await response.json(); + + expect(getDefaultOptions).toHaveBeenCalledWith(apiConfig); + expect(ky.patch).toHaveBeenCalledWith(`inspections/${id}`, { + ...getDefaultOptions(apiConfig), + json: { additional_data: additionalData }, + }); + expect(result).toEqual({ + id: body.id, + response, + body, + }); + }); + }); }); diff --git a/packages/network/test/api/pricing/mappers.test.ts b/packages/network/test/api/pricing/mappers.test.ts new file mode 100644 index 000000000..a540cf28f --- /dev/null +++ b/packages/network/test/api/pricing/mappers.test.ts @@ -0,0 +1,31 @@ +import { MonkEntityType, PricingV2RelatedItemType } from '@monkvision/types'; +import { mapApiPricingPost } from '../../../src/api/pricing/mappers'; +import { ApiPricingV2Details } from '../../../src/api/models'; + +function createApiPricingPost(): ApiPricingV2Details { + return { + id: 'id-test', + related_item_type: PricingV2RelatedItemType.PART, + pricing: 10, + }; +} + +describe('Pricing API Mappers', () => { + const inspectionId = 'test-inspection-id'; + describe('ApiPricingPost mapper', () => { + it('should properly map the ApiPricingPost object', () => { + const apiPricingPostData = createApiPricingPost(); + const result = mapApiPricingPost(inspectionId, createApiPricingPost()); + expect(result).toEqual({ + id: apiPricingPostData.id, + entityType: MonkEntityType.PRICING, + hours: apiPricingPostData.hours, + inspectionId, + operations: apiPricingPostData.operations, + pricing: apiPricingPostData.pricing, + relatedItemId: apiPricingPostData.related_item_id, + relatedItemType: apiPricingPostData.related_item_type, + }); + }); + }); +}); diff --git a/packages/network/test/api/pricing/requests.test.ts b/packages/network/test/api/pricing/requests.test.ts new file mode 100644 index 000000000..8c1605b20 --- /dev/null +++ b/packages/network/test/api/pricing/requests.test.ts @@ -0,0 +1,136 @@ +jest.mock('../../../src/api/config', () => ({ + getDefaultOptions: jest.fn(() => ({ prefixUrl: 'getDefaultOptionsTest' })), +})); +jest.mock('../../../src/api/pricing/mappers', () => ({ + mapApiPricingPost: jest.fn(() => ({ test: 'hello' })), + mapApiPricingPostRequest: jest.fn(() => ({ test: 'hello' })), +})); +jest.mock('ky', () => ({ + post: jest.fn(() => + Promise.resolve({ json: jest.fn(() => Promise.resolve({ id: 'test-fake-id' })) }), + ), + delete: jest.fn(() => + Promise.resolve({ json: jest.fn(() => Promise.resolve({ id: 'test-fake-id' })) }), + ), + patch: jest.fn(() => + Promise.resolve({ json: jest.fn(() => Promise.resolve({ id: 'test-fake-id' })) }), + ), +})); + +import { PricingV2RelatedItemType, VehiclePart } from '@monkvision/types'; +import { + createPricing, + deletePricing, + PricingOptions, + updatePricing, +} from '../../../src/api/pricing'; +import { MonkActionType } from '@monkvision/common'; +import ky from 'ky'; +import { getDefaultOptions } from '../../../src/api/config'; +import { mapApiPricingPost, mapApiPricingPostRequest } from '../../../src/api/pricing/mappers'; + +const apiConfig = { + apiDomain: 'apiDomain', + authToken: 'authToken', + thumbnailDomain: 'thumbnailDomain', +}; + +describe('Pricing requests', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('createPricing request', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + it('should make the proper API call and map the resulting response', async () => { + const id = 'test-inspection-id'; + const dispatch = jest.fn(); + const pricing: PricingOptions = { + type: PricingV2RelatedItemType.PART, + vehiclePart: VehiclePart.ROOF, + pricing: 50, + }; + const result = await createPricing({ id, pricing }, apiConfig, dispatch); + const response = await (ky.post as jest.Mock).mock.results[0].value; + const body = await response.json(); + + const apiPricing = (mapApiPricingPost as jest.Mock).mock.results[0].value; + const apiPricingPost = (mapApiPricingPostRequest as jest.Mock).mock.results[0].value; + expect(mapApiPricingPost).toHaveBeenCalledWith(id, body); + expect(getDefaultOptions).toHaveBeenCalledWith(apiConfig); + const kyOptions = getDefaultOptions(apiConfig); + expect(ky.post).toHaveBeenCalledWith(`inspections/${id}/pricing`, { + ...kyOptions, + json: apiPricingPost, + }); + expect(dispatch).toHaveBeenCalledWith({ + type: MonkActionType.CREATED_ONE_PRICING, + payload: { pricing: apiPricing }, + }); + expect(result).toEqual({ + id: body.id, + response, + body, + }); + }); + }); + + describe('deletePricing request', () => { + it('should make the proper API call and map the resulting response', async () => { + const id = 'test-inspection-id'; + const pricingId = 'test-pricing-id'; + const dispatch = jest.fn(); + const result = await deletePricing({ id, pricingId }, apiConfig, dispatch); + const response = await (ky.delete as jest.Mock).mock.results[0].value; + const body = await response.json(); + + expect(getDefaultOptions).toHaveBeenCalledWith(apiConfig); + const kyOptions = getDefaultOptions(apiConfig); + expect(ky.delete).toHaveBeenCalledWith(`inspections/${id}/pricing/${pricingId}`, { + ...kyOptions, + }); + expect(dispatch).toHaveBeenCalledWith({ + type: MonkActionType.DELETED_ONE_PRICING, + payload: { inspectionId: id, pricingId: body.id }, + }); + expect(result).toEqual({ + id: body.id, + response, + body, + }); + }); + }); + + describe('updatePricing request', () => { + it('should make the proper API call and map the resulting response', async () => { + const id = 'test-inspection-id'; + const pricingId = 'test-pricing-id'; + const pricingMock = 50; + const dispatch = jest.fn(); + const result = await updatePricing({ id, pricingId, price: 50 }, apiConfig, dispatch); + const response = await (ky.patch as jest.Mock).mock.results[0].value; + const body = await response.json(); + + const apiPricing = (mapApiPricingPost as jest.Mock).mock.results[0].value; + expect(getDefaultOptions).toHaveBeenCalledWith(apiConfig); + const kyOptions = getDefaultOptions(apiConfig); + expect(ky.patch).toHaveBeenCalledWith(`inspections/${id}/pricing/${pricingId}`, { + ...kyOptions, + json: { + pricing: pricingMock, + }, + }); + expect(dispatch).toHaveBeenCalledWith({ + type: MonkActionType.UPDATED_ONE_PRICING, + payload: { pricing: apiPricing }, + }); + expect(result).toEqual({ + id: body.id, + response, + body, + }); + }); + }); +}); diff --git a/packages/network/test/api/react.test.ts b/packages/network/test/api/react.test.ts index 1d3c6b22f..064b76a86 100644 --- a/packages/network/test/api/react.test.ts +++ b/packages/network/test/api/react.test.ts @@ -2,6 +2,9 @@ jest.mock('../../src/api/api', () => ({ MonkApi: { getInspection: jest.fn(() => Promise.resolve({ test: 'getInspection' })), createInspection: jest.fn(() => Promise.resolve({ test: 'createInspection' })), + createPricing: jest.fn(() => Promise.resolve({ test: 'createPricing' })), + deletePricing: jest.fn(() => Promise.resolve({ test: 'deletePricing' })), + updatePricing: jest.fn(() => Promise.resolve({ test: 'updatePricing' })), }, })); @@ -47,6 +50,33 @@ describe('Monk API React utilities', () => { requestResultMock = await requestMock.mock.results[0].value; expect(resultMock).toBe(requestResultMock); + dispatchMock.mockClear(); + + param = 'test-createPricing'; + resultMock = await (result.current.createPricing as any)(param); + requestMock = MonkApi.createPricing as jest.Mock; + expect(requestMock).toHaveBeenCalledWith(param, config, dispatchMock); + requestResultMock = await requestMock.mock.results[0].value; + expect(resultMock).toBe(requestResultMock); + + dispatchMock.mockClear(); + + param = 'test-deletePricing'; + resultMock = await (result.current.deletePricing as any)(param); + requestMock = MonkApi.deletePricing as jest.Mock; + expect(requestMock).toHaveBeenCalledWith(param, config, dispatchMock); + requestResultMock = await requestMock.mock.results[0].value; + expect(resultMock).toBe(requestResultMock); + + dispatchMock.mockClear(); + + param = 'test-updatePricing'; + resultMock = await (result.current.updatePricing as any)(param); + requestMock = MonkApi.updatePricing as jest.Mock; + expect(requestMock).toHaveBeenCalledWith(param, config, dispatchMock); + requestResultMock = await requestMock.mock.results[0].value; + expect(resultMock).toBe(requestResultMock); + unmount(); }); diff --git a/packages/types/src/state/pricingV2.ts b/packages/types/src/state/pricingV2.ts index c656d150d..2a374047d 100644 --- a/packages/types/src/state/pricingV2.ts +++ b/packages/types/src/state/pricingV2.ts @@ -34,7 +34,7 @@ export enum RepairOperationType { * Details of the pricing using the expanded pricing feature. Provides details about the operations and the hours of * labour required and the cost of repairs for a specific part or on the entirety of the vehicle. */ -export interface PricingV2Details extends MonkEntity { +export interface PricingV2 extends MonkEntity { /** * The ID of the inspection associated with this pricing information. */ @@ -61,17 +61,3 @@ export interface PricingV2Details extends MonkEntity { */ hours?: Record; } - -/** - * Pricing information for the reparation of a vehicle, in its entirety and for each part. - */ -export interface PricingV2 { - /** - * The details of the pricing information. It associates each element name to its pricing details if it has some. - */ - details: Record; - /** - * The total cost of the reparations. - */ - totalPrice?: number; -}