diff --git a/packages/api-server/api_server/routes/tasks/tasks.py b/packages/api-server/api_server/routes/tasks/tasks.py index 89940ad4b..cbde1e71a 100644 --- a/packages/api-server/api_server/routes/tasks/tasks.py +++ b/packages/api-server/api_server/routes/tasks/tasks.py @@ -251,36 +251,44 @@ async def post_dispatch_task( request.request.category == "compose" and request.request.description is not None and "phases" in request.request.description - and len(request.request.description["phases"]) == 3 - and "on_cancel" in request.request.description["phases"][1] ): - cancellation_lots = await cancellation_lots_from_building_map(logger) - if len(cancellation_lots) != 0: - # Populate them in the correct form - go_to_one_of_the_places_activity = { - "category": "go_to_place", - "description": { - "one_of": [{"waypoint": name} for name in cancellation_lots], - "constraints": [{"category": "prefer_same_map", "description": ""}], - }, - } - delivery_dropoff_activity = { - "category": "perform_action", - "description": { - "unix_millis_action_duration_estimate": 60000, - "category": "delivery_dropoff", - "description": {}, - }, - } - on_cancel_dropoff = { - "category": "sequence", - "description": [ - go_to_one_of_the_places_activity, - delivery_dropoff_activity, - ], - } - # Add into task request - request.request.description["phases"][1]["on_cancel"] = [on_cancel_dropoff] + cancellation_lots = None + for i in range(len(request.request.description["phases"])): + if "on_cancel" not in request.request.description["phases"][i]: + continue + + if cancellation_lots is None: + cancellation_lots = await cancellation_lots_from_building_map(logger) + + if len(cancellation_lots) != 0: + # Populate them in the correct form + go_to_one_of_the_places_activity = { + "category": "go_to_place", + "description": { + "one_of": [{"waypoint": name} for name in cancellation_lots], + "constraints": [ + {"category": "prefer_same_map", "description": ""} + ], + }, + } + delivery_dropoff_activity = { + "category": "perform_action", + "description": { + "unix_millis_action_duration_estimate": 60000, + "category": "delivery_dropoff", + "description": {}, + }, + } + on_cancel_dropoff = { + "category": "sequence", + "description": [ + go_to_one_of_the_places_activity, + delivery_dropoff_activity, + ], + } + request.request.description["phases"][i]["on_cancel"] = [ + on_cancel_dropoff + ] resp = mdl.TaskDispatchResponse.parse_raw( await tasks_service().call(request.json(exclude_none=True)) diff --git a/packages/react-components/lib/tasks/create-task.tsx b/packages/react-components/lib/tasks/create-task.tsx index c8031fc38..cb7bf566d 100644 --- a/packages/react-components/lib/tasks/create-task.tsx +++ b/packages/react-components/lib/tasks/create-task.tsx @@ -67,6 +67,10 @@ import { DeliveryPickupTaskDefinition, DeliverySequentialLotPickupTaskDefinition, DeliveryAreaPickupTaskDefinition, + DoubleComposeDeliveryTaskDescription, + DoubleComposeDeliveryTaskDefinition, + makeDoubleComposeDeliveryTaskBookingLabel, + DoubleComposeDeliveryTaskForm, } from './types/delivery-custom'; import { makePatrolTaskBookingLabel, @@ -89,6 +93,7 @@ export interface TaskDefinition { export type TaskDescription = | DeliveryPickupTaskDescription | DeliveryCustomTaskDescription + | DoubleComposeDeliveryTaskDescription | PatrolTaskDescription | DeliveryTaskDescription | ComposeCleanTaskDescription; @@ -585,6 +590,27 @@ export function CreateTaskForm({ onValidate={onValidate} /> ); + case DoubleComposeDeliveryTaskDefinition.taskDefinitionId: + return ( + { + desc.category = taskRequest.description.category; + desc.phases[0].activity.description.activities[1].description.category = + taskRequest.description.category; + desc.phases[3].activity.description.activities[1].description.category = + taskRequest.description.category; + handleTaskDescriptionChange( + DoubleComposeDeliveryTaskDefinition.requestCategory, + desc, + ); + }} + onValidate={onValidate} + /> + ); case CustomComposeTaskDefinition.taskDefinitionId: return ( )} disabled /> - + - - )} {callToDeleteFavoriteTask && ( diff --git a/packages/react-components/lib/tasks/types/delivery-custom.spec.tsx b/packages/react-components/lib/tasks/types/delivery-custom.spec.tsx index 06cb112cc..ccb559a6d 100644 --- a/packages/react-components/lib/tasks/types/delivery-custom.spec.tsx +++ b/packages/react-components/lib/tasks/types/delivery-custom.spec.tsx @@ -1,370 +1,701 @@ import { - deliveryCustomInsertCartId, - deliveryCustomInsertDropoff, - deliveryCustomInsertOnCancel, - deliveryCustomInsertPickup, + cartPickupPhaseInsertCartId, + cartPickupPhaseInsertPickup, + cartCustomPickupPhaseInsertCartId, + cartCustomPickupPhaseInsertPickup, + CartPickupPhase, DeliveryCustomTaskDescription, - deliveryInsertCartId, - deliveryInsertDropoff, - deliveryInsertOnCancel, - deliveryInsertPickup, + deliveryPhaseInsertDropoff, + deliveryPhaseInsertOnCancel, DeliveryPickupTaskDescription, + DeliveryWithCancellationPhase, + DoubleComposeDeliveryTaskDescription, makeDefaultDeliveryCustomTaskDescription, makeDefaultDeliveryPickupTaskDescription, + makeDefaultDoubleComposeDeliveryTaskDescription, } from '.'; describe('Custom deliveries', () => { - it('delivery pickup', () => { - let deliveryPickupTaskDescription: DeliveryPickupTaskDescription | null = null; - try { - deliveryPickupTaskDescription = JSON.parse(`{ - "category": "delivery_pickup", - "phases": [ - { - "activity": { - "category": "sequence", + it('create valid pickup phase', () => { + const parsedPhase: CartPickupPhase = JSON.parse(`{ + "activity": { + "category": "sequence", + "description": { + "activities": [ + { + "category": "go_to_place", + "description": "test_pickup_place" + }, + { + "category": "perform_action", "description": { - "activities": [ + "unix_millis_action_duration_estimate": 60000, + "category": "delivery_pickup", + "description": { + "cart_id": "test_cart_id", + "pickup_lot": "test_pickup_lot" + } + } + } + ] + } + } + } + `) as CartPickupPhase; + + const defaultDesc = makeDefaultDeliveryPickupTaskDescription(); + let insertedPhase = cartPickupPhaseInsertPickup( + defaultDesc.phases[0], + 'test_pickup_place', + 'test_pickup_lot', + ); + insertedPhase = cartPickupPhaseInsertCartId(insertedPhase, 'test_cart_id'); + expect(parsedPhase).toEqual(insertedPhase); + }); + + it('create valid delivery with cancellation phase', () => { + const parsedPhase: DeliveryWithCancellationPhase = JSON.parse(`{ + "activity": { + "category": "sequence", + "description": { + "activities": [ + { + "category": "go_to_place", + "description": "test_dropoff_place" + } + ] + } + }, + "on_cancel": [ + { + "category": "sequence", + "description": [ + { + "category": "go_to_place", + "description": { + "one_of": [ { - "category": "go_to_place", - "description": "test_pickup_place" + "waypoint": "test_waypoint_1" }, { - "category": "perform_action", - "description": { - "unix_millis_action_duration_estimate": 60000, - "category": "delivery_pickup", - "description": { - "cart_id": "test_cart_id", - "pickup_lot": "test_pickup_lot" - } - } + "waypoint": "test_waypoint_2" + }, + { + "waypoint": "test_waypoint_3" } - ] - } - } - }, - { - "activity": { - "category": "sequence", - "description": { - "activities": [ + ], + "constraints": [ { - "category": "go_to_place", - "description": "test_dropoff_place" + "category": "prefer_same_map", + "description": "" } ] } }, - "on_cancel": [ - { - "category": "sequence", - "description": [ - { - "category": "go_to_place", - "description": { - "one_of": [ - { - "waypoint": "test_waypoint_1" - }, - { - "waypoint": "test_waypoint_2" - }, - { - "waypoint": "test_waypoint_3" - } - ], - "constraints": [ - { - "category": "prefer_same_map", - "description": "" - } - ] - } - }, - { - "category": "perform_action", + { + "category": "perform_action", + "description": { + "unix_millis_action_duration_estimate": 60000, + "category": "delivery_dropoff", + "description": {} + } + } + ] + } + ] + } + `) as DeliveryWithCancellationPhase; + + const defaultDesc = makeDefaultDeliveryPickupTaskDescription(); + let insertedPhase = deliveryPhaseInsertDropoff(defaultDesc.phases[1], 'test_dropoff_place'); + insertedPhase = deliveryPhaseInsertOnCancel(insertedPhase, [ + 'test_waypoint_1', + 'test_waypoint_2', + 'test_waypoint_3', + ]); + expect(parsedPhase).toEqual(insertedPhase); + }); + + it('create valid delivery pickup task description', () => { + const parsedDescription: DeliveryPickupTaskDescription = JSON.parse(`{ + "category": "delivery_pickup", + "phases": [ + { + "activity": { + "category": "sequence", + "description": { + "activities": [ + { + "category": "go_to_place", + "description": "test_pickup_place" + }, + { + "category": "perform_action", + "description": { + "unix_millis_action_duration_estimate": 60000, + "category": "delivery_pickup", "description": { - "unix_millis_action_duration_estimate": 60000, - "category": "delivery_dropoff", - "description": {} + "cart_id": "test_cart_id", + "pickup_lot": "test_pickup_lot" } } - ] - } - ] + } + ] + } + } + }, + { + "activity": { + "category": "sequence", + "description": { + "activities": [ + { + "category": "go_to_place", + "description": "test_dropoff_place" + } + ] + } }, - { - "activity": { + "on_cancel": [ + { "category": "sequence", - "description": { - "activities": [ - { - "category": "perform_action", - "description": { - "unix_millis_action_duration_estimate": 60000, - "category": "delivery_dropoff", - "description": {} - } + "description": [ + { + "category": "go_to_place", + "description": { + "one_of": [ + { + "waypoint": "test_waypoint_1" + }, + { + "waypoint": "test_waypoint_2" + }, + { + "waypoint": "test_waypoint_3" + } + ], + "constraints": [ + { + "category": "prefer_same_map", + "description": "" + } + ] } - ] - } + }, + { + "category": "perform_action", + "description": { + "unix_millis_action_duration_estimate": 60000, + "category": "delivery_dropoff", + "description": {} + } + } + ] + } + ] + }, + { + "activity": { + "category": "sequence", + "description": { + "activities": [ + { + "category": "perform_action", + "description": { + "unix_millis_action_duration_estimate": 60000, + "category": "delivery_dropoff", + "description": {} + } + } + ] } } - ] - } - `) as DeliveryPickupTaskDescription; - } catch (e) { - deliveryPickupTaskDescription = null; + } + ] } - expect(deliveryPickupTaskDescription).not.toEqual(null); + `) as DeliveryPickupTaskDescription; - let description = makeDefaultDeliveryPickupTaskDescription(); - description = deliveryInsertPickup(description, 'test_pickup_place', 'test_pickup_lot'); - description = deliveryInsertCartId(description, 'test_cart_id'); - description = deliveryInsertDropoff(description, 'test_dropoff_place'); - description = deliveryInsertOnCancel(description, [ + const description = makeDefaultDeliveryPickupTaskDescription(); + let pickupPhase = cartPickupPhaseInsertPickup( + description.phases[0], + 'test_pickup_place', + 'test_pickup_lot', + ); + pickupPhase = cartPickupPhaseInsertCartId(pickupPhase, 'test_cart_id'); + let deliveryPhase = deliveryPhaseInsertDropoff(description.phases[1], 'test_dropoff_place'); + deliveryPhase = deliveryPhaseInsertOnCancel(deliveryPhase, [ 'test_waypoint_1', 'test_waypoint_2', 'test_waypoint_3', ]); - expect(deliveryPickupTaskDescription).toEqual(description); + description.phases[0] = pickupPhase; + description.phases[1] = deliveryPhase; + expect(parsedDescription).toEqual(description); }); - it('delivery_sequential_lot_pickup', () => { - let deliveryCustomTaskDescription: DeliveryCustomTaskDescription | null = null; - try { - deliveryCustomTaskDescription = JSON.parse(`{ - "category": "delivery_sequential_lot_pickup", - "phases": [ - { - "activity": { - "category": "sequence", - "description": { - "activities": [ - { - "category": "go_to_place", - "description": "test_pickup_place" - }, - { - "category": "perform_action", + it('create valid delivery_sequential_lot_pickup task description', () => { + const parsedDescription: DeliveryCustomTaskDescription = JSON.parse(`{ + "category": "delivery_sequential_lot_pickup", + "phases": [ + { + "activity": { + "category": "sequence", + "description": { + "activities": [ + { + "category": "go_to_place", + "description": "test_pickup_place" + }, + { + "category": "perform_action", + "description": { + "unix_millis_action_duration_estimate": 60000, + "category": "delivery_sequential_lot_pickup", "description": { - "unix_millis_action_duration_estimate": 60000, - "category": "delivery_sequential_lot_pickup", - "description": { - "cart_id": "test_cart_id", - "pickup_zone": "test_pickup_zone" - } + "cart_id": "test_cart_id", + "pickup_zone": "test_pickup_zone" } } - ] - } + } + ] + } + } + }, + { + "activity": { + "category": "sequence", + "description": { + "activities": [ + { + "category": "go_to_place", + "description": "test_dropoff_place" + } + ] } }, - { - "activity": { + "on_cancel": [ + { "category": "sequence", - "description": { - "activities": [ - { - "category": "go_to_place", - "description": "test_dropoff_place" + "description": [ + { + "category": "go_to_place", + "description": { + "one_of": [ + { + "waypoint": "test_waypoint_1" + }, + { + "waypoint": "test_waypoint_2" + }, + { + "waypoint": "test_waypoint_3" + } + ], + "constraints": [ + { + "category": "prefer_same_map", + "description": "" + } + ] } - ] - } - }, - "on_cancel": [ - { - "category": "sequence", - "description": [ - { - "category": "go_to_place", - "description": { - "one_of": [ - { - "waypoint": "test_waypoint_1" - }, - { - "waypoint": "test_waypoint_2" - }, - { - "waypoint": "test_waypoint_3" - } - ], - "constraints": [ - { - "category": "prefer_same_map", - "description": "" - } - ] - } - }, - { - "category": "perform_action", + }, + { + "category": "perform_action", + "description": { + "unix_millis_action_duration_estimate": 60000, + "category": "delivery_dropoff", + "description": {} + } + } + ] + } + ] + }, + { + "activity": { + "category": "sequence", + "description": { + "activities": [ + { + "category": "perform_action", + "description": { + "unix_millis_action_duration_estimate": 60000, + "category": "delivery_dropoff", + "description": {} + } + } + ] + } + } + } + ] + } + `) as DeliveryCustomTaskDescription; + + const description: DeliveryCustomTaskDescription = makeDefaultDeliveryCustomTaskDescription( + 'delivery_sequential_lot_pickup', + ); + let pickupPhase = cartCustomPickupPhaseInsertPickup( + description.phases[0], + 'test_pickup_place', + 'test_pickup_zone', + ); + pickupPhase = cartCustomPickupPhaseInsertCartId(pickupPhase, 'test_cart_id'); + let deliveryPhase = deliveryPhaseInsertDropoff(description.phases[1], 'test_dropoff_place'); + deliveryPhase = deliveryPhaseInsertOnCancel(deliveryPhase, [ + 'test_waypoint_1', + 'test_waypoint_2', + 'test_waypoint_3', + ]); + description.phases[0] = pickupPhase; + description.phases[1] = deliveryPhase; + expect(parsedDescription).toEqual(description); + }); + + it('create valid delivery_area_pickup task description', () => { + const parsedDescription: DeliveryCustomTaskDescription = JSON.parse(`{ + "category": "delivery_area_pickup", + "phases": [ + { + "activity": { + "category": "sequence", + "description": { + "activities": [ + { + "category": "go_to_place", + "description": "test_pickup_place" + }, + { + "category": "perform_action", + "description": { + "unix_millis_action_duration_estimate": 60000, + "category": "delivery_area_pickup", "description": { - "unix_millis_action_duration_estimate": 60000, - "category": "delivery_dropoff", - "description": {} + "cart_id": "test_cart_id", + "pickup_zone": "test_pickup_zone" } } - ] - } - ] + } + ] + } + } + }, + { + "activity": { + "category": "sequence", + "description": { + "activities": [ + { + "category": "go_to_place", + "description": "test_dropoff_place" + } + ] + } }, - { - "activity": { + "on_cancel": [ + { "category": "sequence", - "description": { - "activities": [ - { - "category": "perform_action", - "description": { - "unix_millis_action_duration_estimate": 60000, - "category": "delivery_dropoff", - "description": {} - } + "description": [ + { + "category": "go_to_place", + "description": { + "one_of": [ + { + "waypoint": "test_waypoint_1" + }, + { + "waypoint": "test_waypoint_2" + }, + { + "waypoint": "test_waypoint_3" + } + ], + "constraints": [ + { + "category": "prefer_same_map", + "description": "" + } + ] } - ] - } + }, + { + "category": "perform_action", + "description": { + "unix_millis_action_duration_estimate": 60000, + "category": "delivery_dropoff", + "description": {} + } + } + ] + } + ] + }, + { + "activity": { + "category": "sequence", + "description": { + "activities": [ + { + "category": "perform_action", + "description": { + "unix_millis_action_duration_estimate": 60000, + "category": "delivery_dropoff", + "description": {} + } + } + ] } } - ] - } - `) as DeliveryCustomTaskDescription; - } catch (e) { - deliveryCustomTaskDescription = null; + } + ] } - expect(deliveryCustomTaskDescription).not.toEqual(null); + `) as DeliveryCustomTaskDescription; - let description: DeliveryCustomTaskDescription = makeDefaultDeliveryCustomTaskDescription( - 'delivery_sequential_lot_pickup', + const description: DeliveryCustomTaskDescription = + makeDefaultDeliveryCustomTaskDescription('delivery_area_pickup'); + let pickupPhase = cartCustomPickupPhaseInsertPickup( + description.phases[0], + 'test_pickup_place', + 'test_pickup_zone', ); - description = deliveryCustomInsertPickup(description, 'test_pickup_place', 'test_pickup_zone'); - description = deliveryCustomInsertCartId(description, 'test_cart_id'); - description = deliveryCustomInsertDropoff(description, 'test_dropoff_place'); - description = deliveryCustomInsertOnCancel(description, [ + pickupPhase = cartCustomPickupPhaseInsertCartId(pickupPhase, 'test_cart_id'); + let deliveryPhase = deliveryPhaseInsertDropoff(description.phases[1], 'test_dropoff_place'); + deliveryPhase = deliveryPhaseInsertOnCancel(deliveryPhase, [ 'test_waypoint_1', 'test_waypoint_2', 'test_waypoint_3', ]); - expect(deliveryCustomTaskDescription).toEqual(description); + description.phases[0] = pickupPhase; + description.phases[1] = deliveryPhase; + expect(parsedDescription).toEqual(description); }); - it('delivery_area_pickup', () => { - let deliveryCustomTaskDescription: DeliveryCustomTaskDescription | null = null; - try { - deliveryCustomTaskDescription = JSON.parse(`{ - "category": "delivery_area_pickup", - "phases": [ - { - "activity": { - "category": "sequence", - "description": { - "activities": [ - { - "category": "go_to_place", - "description": "test_pickup_place" - }, - { - "category": "perform_action", + it('create valid double_compose_delivery task description', () => { + const parsedDescription: DoubleComposeDeliveryTaskDescription = JSON.parse(`{ + "category": "delivery_pickup", + "phases": [ + { + "activity": { + "category": "sequence", + "description": { + "activities": [ + { + "category": "go_to_place", + "description": "test_first_pickup_place" + }, + { + "category": "perform_action", + "description": { + "unix_millis_action_duration_estimate": 60000, + "category": "delivery_pickup", "description": { - "unix_millis_action_duration_estimate": 60000, - "category": "delivery_area_pickup", - "description": { - "cart_id": "test_cart_id", - "pickup_zone": "test_pickup_zone" - } + "cart_id": "test_first_cart_id", + "pickup_lot": "test_first_pickup_lot" } } - ] - } + } + ] + } + } + }, + { + "activity": { + "category": "sequence", + "description": { + "activities": [ + { + "category": "go_to_place", + "description": "test_first_dropoff_place" + } + ] } }, - { - "activity": { + "on_cancel": [ + { "category": "sequence", - "description": { - "activities": [ - { - "category": "go_to_place", - "description": "test_dropoff_place" + "description": [ + { + "category": "go_to_place", + "description": { + "one_of": [ + { + "waypoint": "test_waypoint_1" + }, + { + "waypoint": "test_waypoint_2" + }, + { + "waypoint": "test_waypoint_3" + } + ], + "constraints": [ + { + "category": "prefer_same_map", + "description": "" + } + ] } - ] - } - }, - "on_cancel": [ - { - "category": "sequence", - "description": [ - { - "category": "go_to_place", - "description": { - "one_of": [ - { - "waypoint": "test_waypoint_1" - }, - { - "waypoint": "test_waypoint_2" - }, - { - "waypoint": "test_waypoint_3" - } - ], - "constraints": [ - { - "category": "prefer_same_map", - "description": "" - } - ] - } - }, - { - "category": "perform_action", + }, + { + "category": "perform_action", + "description": { + "unix_millis_action_duration_estimate": 60000, + "category": "delivery_dropoff", + "description": {} + } + } + ] + } + ] + }, + { + "activity": { + "category": "sequence", + "description": { + "activities": [ + { + "category": "perform_action", + "description": { + "unix_millis_action_duration_estimate": 60000, + "category": "delivery_dropoff", + "description": {} + } + } + ] + } + } + }, + { + "activity": { + "category": "sequence", + "description": { + "activities": [ + { + "category": "go_to_place", + "description": "test_second_pickup_place" + }, + { + "category": "perform_action", + "description": { + "unix_millis_action_duration_estimate": 60000, + "category": "delivery_pickup", "description": { - "unix_millis_action_duration_estimate": 60000, - "category": "delivery_dropoff", - "description": {} + "cart_id": "test_second_cart_id", + "pickup_lot": "test_second_pickup_lot" } } - ] - } - ] + } + ] + } + } + }, + { + "activity": { + "category": "sequence", + "description": { + "activities": [ + { + "category": "go_to_place", + "description": "test_second_dropoff_place" + } + ] + } }, - { - "activity": { + "on_cancel": [ + { "category": "sequence", - "description": { - "activities": [ - { - "category": "perform_action", - "description": { - "unix_millis_action_duration_estimate": 60000, - "category": "delivery_dropoff", - "description": {} - } + "description": [ + { + "category": "go_to_place", + "description": { + "one_of": [ + { + "waypoint": "test_waypoint_1" + }, + { + "waypoint": "test_waypoint_2" + }, + { + "waypoint": "test_waypoint_3" + } + ], + "constraints": [ + { + "category": "prefer_same_map", + "description": "" + } + ] } - ] - } + }, + { + "category": "perform_action", + "description": { + "unix_millis_action_duration_estimate": 60000, + "category": "delivery_dropoff", + "description": {} + } + } + ] + } + ] + }, + { + "activity": { + "category": "sequence", + "description": { + "activities": [ + { + "category": "perform_action", + "description": { + "unix_millis_action_duration_estimate": 60000, + "category": "delivery_dropoff", + "description": {} + } + } + ] } } - ] - } - `) as DeliveryCustomTaskDescription; - } catch (e) { - deliveryCustomTaskDescription = null; + } + ] } - expect(deliveryCustomTaskDescription).not.toEqual(null); + `) as DoubleComposeDeliveryTaskDescription; - let description: DeliveryCustomTaskDescription = - makeDefaultDeliveryCustomTaskDescription('delivery_area_pickup'); - description = deliveryCustomInsertPickup(description, 'test_pickup_place', 'test_pickup_zone'); - description = deliveryCustomInsertCartId(description, 'test_cart_id'); - description = deliveryCustomInsertDropoff(description, 'test_dropoff_place'); - description = deliveryCustomInsertOnCancel(description, [ + const description: DoubleComposeDeliveryTaskDescription = + makeDefaultDoubleComposeDeliveryTaskDescription(); + let firstPickupPhase = cartPickupPhaseInsertPickup( + description.phases[0], + 'test_first_pickup_place', + 'test_first_pickup_lot', + ); + firstPickupPhase = cartPickupPhaseInsertCartId(firstPickupPhase, 'test_first_cart_id'); + let firstDeliveryPhase = deliveryPhaseInsertDropoff( + description.phases[1], + 'test_first_dropoff_place', + ); + firstDeliveryPhase = deliveryPhaseInsertOnCancel(firstDeliveryPhase, [ + 'test_waypoint_1', + 'test_waypoint_2', + 'test_waypoint_3', + ]); + let secondPickupPhase = cartPickupPhaseInsertPickup( + description.phases[3], + 'test_second_pickup_place', + 'test_second_pickup_lot', + ); + secondPickupPhase = cartPickupPhaseInsertCartId(secondPickupPhase, 'test_second_cart_id'); + let secondDeliveryPhase = deliveryPhaseInsertDropoff( + description.phases[4], + 'test_second_dropoff_place', + ); + secondDeliveryPhase = deliveryPhaseInsertOnCancel(secondDeliveryPhase, [ 'test_waypoint_1', 'test_waypoint_2', 'test_waypoint_3', ]); - expect(deliveryCustomTaskDescription).toEqual(description); + description.phases[0] = firstPickupPhase; + description.phases[1] = firstDeliveryPhase; + description.phases[3] = secondPickupPhase; + description.phases[4] = secondDeliveryPhase; + expect(parsedDescription).toEqual(description); }); }); diff --git a/packages/react-components/lib/tasks/types/delivery-custom.tsx b/packages/react-components/lib/tasks/types/delivery-custom.tsx index 5bebe359c..1a0320c13 100644 --- a/packages/react-components/lib/tasks/types/delivery-custom.tsx +++ b/packages/react-components/lib/tasks/types/delivery-custom.tsx @@ -1,6 +1,5 @@ import { Autocomplete, Grid, TextField, useMediaQuery, useTheme } from '@mui/material'; import React from 'react'; -import { isNonEmptyString } from './utils'; import type { TaskBookingLabel } from 'api-client'; import { TaskDefinition } from '../create-task'; @@ -22,6 +21,12 @@ export const DeliveryAreaPickupTaskDefinition: TaskDefinition = { requestCategory: 'compose', }; +export const DoubleComposeDeliveryTaskDefinition: TaskDefinition = { + taskDefinitionId: 'double-compose-delivery', + taskDisplayName: 'Double Compose Delivery', + requestCategory: 'compose', +}; + export interface LotPickupActivity { category: string; description: { @@ -60,7 +65,7 @@ interface CartCustomPickupPhase { }; } -interface CartPickupPhase { +export interface CartPickupPhase { activity: { category: string; description: { @@ -103,7 +108,7 @@ interface OnCancelDropoff { ]; } -interface DeliveryWithCancellationPhase { +export interface DeliveryWithCancellationPhase { activity: { category: string; description: { @@ -140,6 +145,18 @@ export interface DeliveryPickupTaskDescription { ]; } +export interface DoubleComposeDeliveryTaskDescription { + category: string; + phases: [ + first_pickup_phase: CartPickupPhase, + first_delivery_phase: DeliveryWithCancellationPhase, + first_dropoff_phase: CartDropoffPhase, + second_pickup_phase: CartPickupPhase, + second_delivery_phase: DeliveryWithCancellationPhase, + second_dropoff_phase: CartDropoffPhase, + ]; +} + export function makeDeliveryPickupTaskBookingLabel( task_description: DeliveryPickupTaskDescription, ): TaskBookingLabel { @@ -170,6 +187,28 @@ export function makeDeliveryCustomTaskBookingLabel( }; } +export function makeDoubleComposeDeliveryTaskBookingLabel( + task_description: DoubleComposeDeliveryTaskDescription, +): TaskBookingLabel { + const firstPickupDescription = + task_description.phases[0].activity.description.activities[1].description.description; + const secondPickupDescription = + task_description.phases[3].activity.description.activities[1].description.description; + + const pickups = `${firstPickupDescription.pickup_lot}(1), ${secondPickupDescription.pickup_lot}(2)`; + const dropoffs = `${task_description.phases[1].activity.description.activities[0].description}(1), ${task_description.phases[4].activity.description.activities[0].description}(2)`; + const cartIds = `${firstPickupDescription.pickup_lot}(1), ${secondPickupDescription.cart_id}(2)`; + + return { + description: { + task_definition_id: task_description.category, + pickup: pickups, + destination: dropoffs, + cart_id: cartIds, + }, + }; +} + export function makeDeliveryPickupTaskShortDescription( desc: DeliveryPickupTaskDescription, taskDisplayName: string | undefined, @@ -198,6 +237,43 @@ export function makeDeliveryPickupTaskShortDescription( return '[Unknown] delivery pickup task'; } +export function makeDoubleComposeDeliveryTaskShortDescription( + desc: DoubleComposeDeliveryTaskDescription, + taskDisplayName: string | undefined, +): string { + try { + const firstGoToPickup: GoToPlaceActivity = desc.phases[0].activity.description.activities[0]; + const firstPickup: LotPickupActivity = desc.phases[0].activity.description.activities[1]; + const firstCartId = firstPickup.description.description.cart_id; + const firstGoToDropoff: GoToPlaceActivity = desc.phases[1].activity.description.activities[0]; + + const secondGoToPickup: GoToPlaceActivity = desc.phases[3].activity.description.activities[0]; + const secondPickup: LotPickupActivity = desc.phases[3].activity.description.activities[1]; + const secondCartId = secondPickup.description.description.cart_id; + const secondGoToDropoff: GoToPlaceActivity = desc.phases[4].activity.description.activities[0]; + + return `[${ + taskDisplayName ?? DeliveryPickupTaskDefinition.taskDisplayName + }] payload [${firstCartId}] from [${firstGoToPickup.description}] to [${ + firstGoToDropoff.description + }], then payload [${secondCartId}] from [${secondGoToPickup.description}] to [${ + secondGoToDropoff.description + }]`; + } catch (e) { + try { + const descriptionString = JSON.stringify(desc); + console.error(descriptionString); + return descriptionString; + } catch (e) { + console.error( + `Failed to parse task description of double compose delivery task: ${(e as Error).message}`, + ); + } + } + + return '[Unknown] double compose delivery task'; +} + export function makeDeliveryCustomTaskShortDescription( desc: DeliveryCustomTaskDescription, taskDisplayName: string | undefined, @@ -246,15 +322,45 @@ const isDeliveryPickupTaskDescriptionValid = ( const pickup = taskDescription.phases[0].activity.description.activities[1]; const goToDropoff = taskDescription.phases[1].activity.description.activities[0]; return ( - isNonEmptyString(goToPickup.description) && + goToPickup.description.length > 0 && Object.keys(pickupPoints).includes(goToPickup.description) && pickupPoints[goToPickup.description] === pickup.description.description.pickup_lot && - isNonEmptyString(pickup.description.description.cart_id) && - isNonEmptyString(goToDropoff.description) && + pickup.description.description.cart_id.length > 0 && + goToDropoff.description.length > 0 && Object.keys(dropoffPoints).includes(goToDropoff.description) ); }; +const isDoubleComposeDeliveryTaskDescriptionValid = ( + taskDescription: DoubleComposeDeliveryTaskDescription, + pickupPoints: Record, + dropoffPoints: Record, +): boolean => { + const firstGoToPickup = taskDescription.phases[0].activity.description.activities[0]; + const firstPickup = taskDescription.phases[0].activity.description.activities[1]; + const firstGoToDropoff = taskDescription.phases[1].activity.description.activities[0]; + + const secondGoToPickup = taskDescription.phases[3].activity.description.activities[0]; + const secondPickup = taskDescription.phases[3].activity.description.activities[1]; + const secondGoToDropoff = taskDescription.phases[4].activity.description.activities[0]; + + return ( + firstGoToPickup.description.length > 0 && + Object.keys(pickupPoints).includes(firstGoToPickup.description) && + pickupPoints[firstGoToPickup.description] === firstPickup.description.description.pickup_lot && + firstPickup.description.description.cart_id.length > 0 && + firstGoToDropoff.description.length > 0 && + Object.keys(dropoffPoints).includes(firstGoToDropoff.description) && + secondGoToPickup.description.length > 0 && + Object.keys(pickupPoints).includes(secondGoToPickup.description) && + pickupPoints[secondGoToPickup.description] === + secondPickup.description.description.pickup_lot && + secondPickup.description.description.cart_id.length > 0 && + secondGoToDropoff.description.length > 0 && + Object.keys(dropoffPoints).includes(secondGoToDropoff.description) + ); +}; + const isDeliveryCustomTaskDescriptionValid = ( taskDescription: DeliveryCustomTaskDescription, pickupZones: string[], @@ -264,47 +370,45 @@ const isDeliveryCustomTaskDescriptionValid = ( const pickup = taskDescription.phases[0].activity.description.activities[1]; const goToDropoff = taskDescription.phases[1].activity.description.activities[0]; return ( - isNonEmptyString(goToPickup.description) && - isNonEmptyString(pickup.description.description.pickup_zone) && + goToPickup.description.length > 0 && + pickup.description.description.pickup_zone.length > 0 && pickupZones.includes(pickup.description.description.pickup_zone) && - isNonEmptyString(pickup.description.description.cart_id) && - isNonEmptyString(goToDropoff.description) && + pickup.description.description.cart_id.length > 0 && + goToDropoff.description.length > 0 && dropoffPoints.includes(goToDropoff.description) ); }; -export function deliveryInsertPickup( - taskDescription: DeliveryPickupTaskDescription, +export function cartPickupPhaseInsertPickup( + phase: CartPickupPhase, pickupPlace: string, pickupLot: string, -): DeliveryPickupTaskDescription { - taskDescription.phases[0].activity.description.activities[0].description = pickupPlace; - taskDescription.phases[0].activity.description.activities[1].description.description.pickup_lot = - pickupLot; - return taskDescription; +): CartPickupPhase { + phase.activity.description.activities[0].description = pickupPlace; + phase.activity.description.activities[1].description.description.pickup_lot = pickupLot; + return phase; } -export function deliveryInsertCartId( - taskDescription: DeliveryPickupTaskDescription, +export function cartPickupPhaseInsertCartId( + phase: CartPickupPhase, cartId: string, -): DeliveryPickupTaskDescription { - taskDescription.phases[0].activity.description.activities[1].description.description.cart_id = - cartId; - return taskDescription; +): CartPickupPhase { + phase.activity.description.activities[1].description.description.cart_id = cartId; + return phase; } -export function deliveryInsertDropoff( - taskDescription: DeliveryPickupTaskDescription, +export function deliveryPhaseInsertDropoff( + phase: DeliveryWithCancellationPhase, dropoffPlace: string, -): DeliveryPickupTaskDescription { - taskDescription.phases[1].activity.description.activities[0].description = dropoffPlace; - return taskDescription; +): DeliveryWithCancellationPhase { + phase.activity.description.activities[0].description = dropoffPlace; + return phase; } -export function deliveryInsertOnCancel( - taskDescription: DeliveryPickupTaskDescription, +export function deliveryPhaseInsertOnCancel( + phase: DeliveryWithCancellationPhase, onCancelPlaces: string[], -): DeliveryPickupTaskDescription { +): DeliveryWithCancellationPhase { const goToOneOfThePlaces: GoToOneOfThePlacesActivity = { category: 'go_to_place', description: { @@ -333,8 +437,8 @@ export function deliveryInsertOnCancel( category: 'sequence', description: [goToOneOfThePlaces, deliveryDropoff], }; - taskDescription.phases[1].on_cancel = [onCancelDropoff]; - return taskDescription; + phase.on_cancel = [onCancelDropoff]; + return phase; } interface DeliveryPickupTaskFormProps { @@ -372,15 +476,23 @@ export function DeliveryPickupTaskForm({ value={taskDesc.phases[0].activity.description.activities[0].description} onInputChange={(_ev, newValue) => { const pickupLot = pickupPoints[newValue] ?? ''; - let newTaskDesc = { ...taskDesc }; - newTaskDesc = deliveryInsertPickup(newTaskDesc, newValue, pickupLot); + const newTaskDesc = { ...taskDesc }; + newTaskDesc.phases[0] = cartPickupPhaseInsertPickup( + newTaskDesc.phases[0], + newValue, + pickupLot, + ); onInputChange(newTaskDesc); }} onBlur={(ev) => { const place = (ev.target as HTMLInputElement).value; const pickupLot = pickupPoints[place] ?? ''; - let newTaskDesc = { ...taskDesc }; - newTaskDesc = deliveryInsertPickup(newTaskDesc, place, pickupLot); + const newTaskDesc = { ...taskDesc }; + newTaskDesc.phases[0] = cartPickupPhaseInsertPickup( + newTaskDesc.phases[0], + place, + pickupLot, + ); onInputChange(newTaskDesc); }} sx={{ @@ -415,13 +527,16 @@ export function DeliveryPickupTaskForm({ } getOptionLabel={(option) => option} onInputChange={(_ev, newValue) => { - let newTaskDesc = { ...taskDesc }; - newTaskDesc = deliveryInsertCartId(newTaskDesc, newValue); + const newTaskDesc = { ...taskDesc }; + newTaskDesc.phases[0] = cartPickupPhaseInsertCartId(newTaskDesc.phases[0], newValue); onInputChange(newTaskDesc); }} onBlur={(ev) => { - let newTaskDesc = { ...taskDesc }; - newTaskDesc = deliveryInsertCartId(newTaskDesc, (ev.target as HTMLInputElement).value); + const newTaskDesc = { ...taskDesc }; + newTaskDesc.phases[0] = cartPickupPhaseInsertCartId( + newTaskDesc.phases[0], + (ev.target as HTMLInputElement).value, + ); onInputChange(newTaskDesc); }} sx={{ @@ -452,13 +567,16 @@ export function DeliveryPickupTaskForm({ options={Object.keys(dropoffPoints).sort()} value={taskDesc.phases[1].activity.description.activities[0].description} onInputChange={(_ev, newValue) => { - let newTaskDesc = { ...taskDesc }; - newTaskDesc = deliveryInsertDropoff(newTaskDesc, newValue); + const newTaskDesc = { ...taskDesc }; + newTaskDesc.phases[1] = deliveryPhaseInsertDropoff(newTaskDesc.phases[1], newValue); onInputChange(newTaskDesc); }} onBlur={(ev) => { - let newTaskDesc = { ...taskDesc }; - newTaskDesc = deliveryInsertDropoff(newTaskDesc, (ev.target as HTMLInputElement).value); + const newTaskDesc = { ...taskDesc }; + newTaskDesc.phases[1] = deliveryPhaseInsertDropoff( + newTaskDesc.phases[1], + (ev.target as HTMLInputElement).value, + ); onInputChange(newTaskDesc); }} sx={{ @@ -486,71 +604,321 @@ export function DeliveryPickupTaskForm({ ); } -export function deliveryCustomInsertPickup( - taskDescription: DeliveryCustomTaskDescription, - pickupPlace: string, - pickupZone: string, -): DeliveryCustomTaskDescription { - taskDescription.phases[0].activity.description.activities[0].description = pickupPlace; - taskDescription.phases[0].activity.description.activities[1].description.description.pickup_zone = - pickupZone; - return taskDescription; +interface DoubleComposeDeliveryTaskFormProps { + taskDesc: DoubleComposeDeliveryTaskDescription; + pickupPoints: Record; + cartIds: string[]; + dropoffPoints: Record; + onChange(taskDesc: DoubleComposeDeliveryTaskDescription): void; + onValidate(valid: boolean): void; } -export function deliveryCustomInsertCartId( - taskDescription: DeliveryCustomTaskDescription, - cartId: string, -): DeliveryCustomTaskDescription { - taskDescription.phases[0].activity.description.activities[1].description.description.cart_id = - cartId; - return taskDescription; +export function DoubleComposeDeliveryTaskForm({ + taskDesc, + pickupPoints = {}, + cartIds = [], + dropoffPoints = {}, + onChange, + onValidate, +}: DoubleComposeDeliveryTaskFormProps): React.JSX.Element { + const theme = useTheme(); + const isScreenHeightLessThan800 = useMediaQuery('(max-height:800px)'); + const onInputChange = (desc: DoubleComposeDeliveryTaskDescription) => { + onValidate(isDoubleComposeDeliveryTaskDescriptionValid(desc, pickupPoints, dropoffPoints)); + onChange(desc); + }; + + return ( + + + { + const pickupLot = pickupPoints[newValue] ?? ''; + const newTaskDesc = { ...taskDesc }; + newTaskDesc.phases[0] = cartPickupPhaseInsertPickup( + newTaskDesc.phases[0], + newValue, + pickupLot, + ); + onInputChange(newTaskDesc); + }} + onBlur={(ev) => { + const place = (ev.target as HTMLInputElement).value; + const pickupLot = pickupPoints[place] ?? ''; + const newTaskDesc = { ...taskDesc }; + newTaskDesc.phases[0] = cartPickupPhaseInsertPickup( + newTaskDesc.phases[0], + place, + pickupLot, + ); + onInputChange(newTaskDesc); + }} + sx={{ + '& .MuiOutlinedInput-root': { + height: isScreenHeightLessThan800 ? '3rem' : '3.5rem', + fontSize: isScreenHeightLessThan800 ? 14 : 20, + }, + }} + renderInput={(params) => ( + + )} + /> + + + { + const newTaskDesc = { ...taskDesc }; + newTaskDesc.phases[1] = deliveryPhaseInsertDropoff(newTaskDesc.phases[1], newValue); + onInputChange(newTaskDesc); + }} + onBlur={(ev) => { + const newTaskDesc = { ...taskDesc }; + newTaskDesc.phases[1] = deliveryPhaseInsertDropoff( + newTaskDesc.phases[1], + (ev.target as HTMLInputElement).value, + ); + onInputChange(newTaskDesc); + }} + sx={{ + '& .MuiOutlinedInput-root': { + height: isScreenHeightLessThan800 ? '3rem' : '3.5rem', + fontSize: isScreenHeightLessThan800 ? 14 : 20, + }, + }} + renderInput={(params) => ( + + )} + /> + + + option} + onInputChange={(_ev, newValue) => { + const newTaskDesc = { ...taskDesc }; + newTaskDesc.phases[0] = cartPickupPhaseInsertCartId(newTaskDesc.phases[0], newValue); + onInputChange(newTaskDesc); + }} + onBlur={(ev) => { + const newTaskDesc = { ...taskDesc }; + newTaskDesc.phases[0] = cartPickupPhaseInsertCartId( + newTaskDesc.phases[0], + (ev.target as HTMLInputElement).value, + ); + onInputChange(newTaskDesc); + }} + sx={{ + '& .MuiOutlinedInput-root': { + height: isScreenHeightLessThan800 ? '3rem' : '3.5rem', + fontSize: isScreenHeightLessThan800 ? 14 : 20, + }, + }} + renderInput={(params) => ( + + )} + /> + + + { + const pickupLot = pickupPoints[newValue] ?? ''; + const newTaskDesc = { ...taskDesc }; + newTaskDesc.phases[3] = cartPickupPhaseInsertPickup( + newTaskDesc.phases[3], + newValue, + pickupLot, + ); + onInputChange(newTaskDesc); + }} + onBlur={(ev) => { + const place = (ev.target as HTMLInputElement).value; + const pickupLot = pickupPoints[place] ?? ''; + const newTaskDesc = { ...taskDesc }; + newTaskDesc.phases[3] = cartPickupPhaseInsertPickup( + newTaskDesc.phases[3], + place, + pickupLot, + ); + onInputChange(newTaskDesc); + }} + sx={{ + '& .MuiOutlinedInput-root': { + height: isScreenHeightLessThan800 ? '3rem' : '3.5rem', + fontSize: isScreenHeightLessThan800 ? 14 : 20, + }, + }} + renderInput={(params) => ( + + )} + /> + + + { + const newTaskDesc = { ...taskDesc }; + newTaskDesc.phases[4] = deliveryPhaseInsertDropoff(newTaskDesc.phases[4], newValue); + onInputChange(newTaskDesc); + }} + onBlur={(ev) => { + const newTaskDesc = { ...taskDesc }; + newTaskDesc.phases[4] = deliveryPhaseInsertDropoff( + newTaskDesc.phases[4], + (ev.target as HTMLInputElement).value, + ); + onInputChange(newTaskDesc); + }} + sx={{ + '& .MuiOutlinedInput-root': { + height: isScreenHeightLessThan800 ? '3rem' : '3.5rem', + fontSize: isScreenHeightLessThan800 ? 14 : 20, + }, + }} + renderInput={(params) => ( + + )} + /> + + + option} + onInputChange={(_ev, newValue) => { + const newTaskDesc = { ...taskDesc }; + newTaskDesc.phases[3] = cartPickupPhaseInsertCartId(newTaskDesc.phases[3], newValue); + onInputChange(newTaskDesc); + }} + onBlur={(ev) => { + const newTaskDesc = { ...taskDesc }; + newTaskDesc.phases[3] = cartPickupPhaseInsertCartId( + newTaskDesc.phases[3], + (ev.target as HTMLInputElement).value, + ); + onInputChange(newTaskDesc); + }} + sx={{ + '& .MuiOutlinedInput-root': { + height: isScreenHeightLessThan800 ? '3rem' : '3.5rem', + fontSize: isScreenHeightLessThan800 ? 14 : 20, + }, + }} + renderInput={(params) => ( + + )} + /> + + + ); } -export function deliveryCustomInsertDropoff( - taskDescription: DeliveryCustomTaskDescription, - dropoffPlace: string, -): DeliveryCustomTaskDescription { - taskDescription.phases[1].activity.description.activities[0].description = dropoffPlace; - return taskDescription; +export function cartCustomPickupPhaseInsertPickup( + phase: CartCustomPickupPhase, + pickupPlace: string, + pickupZone: string, +): CartCustomPickupPhase { + phase.activity.description.activities[0].description = pickupPlace; + phase.activity.description.activities[1].description.description.pickup_zone = pickupZone; + return phase; } -export function deliveryCustomInsertOnCancel( - taskDescription: DeliveryCustomTaskDescription, - onCancelPlaces: string[], -): DeliveryCustomTaskDescription { - const goToOneOfThePlaces: GoToOneOfThePlacesActivity = { - category: 'go_to_place', - description: { - one_of: onCancelPlaces.map((placeName) => { - return { - waypoint: placeName, - }; - }), - constraints: [ - { - category: 'prefer_same_map', - description: '', - }, - ], - }, - }; - const deliveryDropoff: DropoffActivity = { - category: 'perform_action', - description: { - unix_millis_action_duration_estimate: 60000, - category: 'delivery_dropoff', - description: {}, - }, - }; - const onCancelDropoff: OnCancelDropoff = { - category: 'sequence', - description: [goToOneOfThePlaces, deliveryDropoff], - }; - taskDescription.phases[1].on_cancel = [onCancelDropoff]; - return taskDescription; +export function cartCustomPickupPhaseInsertCartId( + phase: CartCustomPickupPhase, + cartId: string, +): CartCustomPickupPhase { + phase.activity.description.activities[1].description.description.cart_id = cartId; + return phase; } -interface DeliveryCustomProps { +export interface DeliveryCustomProps { taskDesc: DeliveryCustomTaskDescription; pickupZones: string[]; cartIds: string[]; @@ -584,14 +952,22 @@ export function DeliveryCustomTaskForm({ options={pickupZones.sort()} value={taskDesc.phases[0].activity.description.activities[0].description} onInputChange={(_ev, newValue) => { - let newTaskDesc = { ...taskDesc }; - newTaskDesc = deliveryCustomInsertPickup(newTaskDesc, newValue, newValue); + const newTaskDesc = { ...taskDesc }; + newTaskDesc.phases[0] = cartCustomPickupPhaseInsertPickup( + newTaskDesc.phases[0], + newValue, + newValue, + ); onInputChange(newTaskDesc); }} onBlur={(ev) => { const zone = (ev.target as HTMLInputElement).value; - let newTaskDesc = { ...taskDesc }; - newTaskDesc = deliveryCustomInsertPickup(newTaskDesc, zone, zone); + const newTaskDesc = { ...taskDesc }; + newTaskDesc.phases[0] = cartCustomPickupPhaseInsertPickup( + newTaskDesc.phases[0], + zone, + zone, + ); onInputChange(newTaskDesc); }} sx={{ @@ -626,14 +1002,17 @@ export function DeliveryCustomTaskForm({ } getOptionLabel={(option) => option} onInputChange={(_ev, newValue) => { - let newTaskDesc = { ...taskDesc }; - newTaskDesc = deliveryCustomInsertCartId(newTaskDesc, newValue); + const newTaskDesc = { ...taskDesc }; + newTaskDesc.phases[0] = cartCustomPickupPhaseInsertCartId( + newTaskDesc.phases[0], + newValue, + ); onInputChange(newTaskDesc); }} onBlur={(ev) => { - let newTaskDesc = { ...taskDesc }; - newTaskDesc = deliveryCustomInsertCartId( - newTaskDesc, + const newTaskDesc = { ...taskDesc }; + newTaskDesc.phases[0] = cartCustomPickupPhaseInsertCartId( + newTaskDesc.phases[0], (ev.target as HTMLInputElement).value, ); onInputChange(newTaskDesc); @@ -666,14 +1045,14 @@ export function DeliveryCustomTaskForm({ options={dropoffPoints.sort()} value={taskDesc.phases[1].activity.description.activities[0].description} onInputChange={(_ev, newValue) => { - let newTaskDesc = { ...taskDesc }; - newTaskDesc = deliveryCustomInsertDropoff(newTaskDesc, newValue); + const newTaskDesc = { ...taskDesc }; + newTaskDesc.phases[1] = deliveryPhaseInsertDropoff(newTaskDesc.phases[1], newValue); onInputChange(newTaskDesc); }} onBlur={(ev) => { - let newTaskDesc = { ...taskDesc }; - newTaskDesc = deliveryCustomInsertDropoff( - newTaskDesc, + const newTaskDesc = { ...taskDesc }; + newTaskDesc.phases[1] = deliveryPhaseInsertDropoff( + newTaskDesc.phases[1], (ev.target as HTMLInputElement).value, ); onInputChange(newTaskDesc); @@ -766,6 +1145,124 @@ export function makeDefaultDeliveryPickupTaskDescription(): DeliveryPickupTaskDe }; } +export function makeDefaultDoubleComposeDeliveryTaskDescription(): DoubleComposeDeliveryTaskDescription { + return { + category: 'delivery_pickup', + phases: [ + { + activity: { + category: 'sequence', + description: { + activities: [ + { + category: 'go_to_place', + description: '', + }, + { + category: 'perform_action', + description: { + unix_millis_action_duration_estimate: 60000, + category: 'delivery_pickup', + description: { + cart_id: '', + pickup_lot: '', + }, + }, + }, + ], + }, + }, + }, + { + activity: { + category: 'sequence', + description: { + activities: [ + { + category: 'go_to_place', + description: '', + }, + ], + }, + }, + on_cancel: [], + }, + { + activity: { + category: 'sequence', + description: { + activities: [ + { + category: 'perform_action', + description: { + unix_millis_action_duration_estimate: 60000, + category: 'delivery_dropoff', + description: {}, + }, + }, + ], + }, + }, + }, + { + activity: { + category: 'sequence', + description: { + activities: [ + { + category: 'go_to_place', + description: '', + }, + { + category: 'perform_action', + description: { + unix_millis_action_duration_estimate: 60000, + category: 'delivery_pickup', + description: { + cart_id: '', + pickup_lot: '', + }, + }, + }, + ], + }, + }, + }, + { + activity: { + category: 'sequence', + description: { + activities: [ + { + category: 'go_to_place', + description: '', + }, + ], + }, + }, + on_cancel: [], + }, + { + activity: { + category: 'sequence', + description: { + activities: [ + { + category: 'perform_action', + description: { + unix_millis_action_duration_estimate: 60000, + category: 'delivery_dropoff', + description: {}, + }, + }, + ], + }, + }, + }, + ], + }; +} + export function makeDefaultDeliveryCustomTaskDescription( taskCategory: string, ): DeliveryCustomTaskDescription { diff --git a/packages/react-components/lib/tasks/types/delivery.tsx b/packages/react-components/lib/tasks/types/delivery.tsx index fdc32e24c..c9ce3e75b 100644 --- a/packages/react-components/lib/tasks/types/delivery.tsx +++ b/packages/react-components/lib/tasks/types/delivery.tsx @@ -1,4 +1,3 @@ -import { isNonEmptyString, isPositiveNumber } from './utils'; import { Autocomplete, Grid, TextField, useTheme } from '@mui/material'; import { PositiveIntField } from '../../form-inputs'; import React from 'react'; @@ -40,10 +39,10 @@ export function makeDeliveryTaskBookingLabel( function isTaskPlaceValid(place: TaskPlace): boolean { return ( - isNonEmptyString(place.place) && - isNonEmptyString(place.handler) && - isNonEmptyString(place.payload.sku) && - isPositiveNumber(place.payload.quantity) + place.place.length > 0 && + place.handler.length > 0 && + place.payload.sku.length > 0 && + place.payload.quantity > 0 ); } diff --git a/packages/react-components/lib/tasks/types/utils.ts b/packages/react-components/lib/tasks/types/utils.ts index d91285bf7..6e6208da4 100644 --- a/packages/react-components/lib/tasks/types/utils.ts +++ b/packages/react-components/lib/tasks/types/utils.ts @@ -8,10 +8,13 @@ import { DeliveryAreaPickupTaskDefinition, DeliveryPickupTaskDefinition, DeliverySequentialLotPickupTaskDefinition, + DoubleComposeDeliveryTaskDefinition, makeDeliveryPickupTaskShortDescription, makeDeliveryCustomTaskShortDescription, makeDefaultDeliveryCustomTaskDescription, makeDefaultDeliveryPickupTaskDescription, + makeDefaultDoubleComposeDeliveryTaskDescription, + makeDoubleComposeDeliveryTaskShortDescription, } from './delivery-custom'; import { getTaskBookingLabelFromTaskRequest } from '../task-booking-label-utils'; import { @@ -30,14 +33,6 @@ import { } from './custom-compose'; import { TaskDefinition, TaskDescription } from '../create-task'; -export function isNonEmptyString(value: string): boolean { - return value.length > 0; -} - -export function isPositiveNumber(value: number): boolean { - return value > 0; -} - function rawStringFromJsonRequest(taskRequest: TaskRequest): string | undefined { try { const requestString = JSON.stringify(taskRequest); @@ -77,6 +72,11 @@ export function getShortDescription( case DeliverySequentialLotPickupTaskDefinition.taskDefinitionId: case DeliveryAreaPickupTaskDefinition.taskDefinitionId: return makeDeliveryCustomTaskShortDescription(taskRequest.description, taskDisplayName); + case DoubleComposeDeliveryTaskDefinition.taskDefinitionId: + return makeDoubleComposeDeliveryTaskShortDescription( + taskRequest.description, + taskDisplayName, + ); case CustomComposeTaskDefinition.taskDefinitionId: return makeCustomComposeTaskShortDescription(taskRequest.description); default: @@ -94,6 +94,8 @@ export function getDefaultTaskDefinition(taskDefinitionId: string): TaskDefiniti return DeliverySequentialLotPickupTaskDefinition; case DeliveryAreaPickupTaskDefinition.taskDefinitionId: return DeliveryAreaPickupTaskDefinition; + case DoubleComposeDeliveryTaskDefinition.taskDefinitionId: + return DoubleComposeDeliveryTaskDefinition; case DeliveryTaskDefinition.taskDefinitionId: return DeliveryTaskDefinition; case PatrolTaskDefinition.taskDefinitionId: @@ -115,6 +117,8 @@ export function getDefaultTaskDescription( case DeliverySequentialLotPickupTaskDefinition.taskDefinitionId: case DeliveryAreaPickupTaskDefinition.taskDefinitionId: return makeDefaultDeliveryCustomTaskDescription(taskDefinitionId); + case DoubleComposeDeliveryTaskDefinition.taskDefinitionId: + return makeDefaultDoubleComposeDeliveryTaskDescription(); case DeliveryTaskDefinition.taskDefinitionId: return makeDefaultDeliveryTaskDescription(); case PatrolTaskDefinition.taskDefinitionId: