Untitled

 avatar
unknown
typescript
5 months ago
16 kB
15
Indexable
import config from "@mercadoni/elementals/config";
import moment, { Moment } from "moment";
import jwt from "jsonwebtoken";
import { DynamicParameterValue } from "../../../entities/DynamicParameter";
import Job, { JobEventSource } from "../../../entities/Job";
import { getAbsoluteDifferenceInMinutes } from "../../../utils/Dates";
import InvariantViolation from "../../../utils/errors/InvariantViolation";
import { JobWithEvents } from "../../../interactors/InteractorResponseModel";
import Event, { EventType } from "../../../entities/Event";
import Task, { TaskType } from "../../../entities/Task";
import JobQuote from "../../../entities/JobQuote";
import { getEstimatedStepDurationInMinutes } from "../creation/CalculateEstimatedDurations";
import { OperationalModel, StartTimeByTask } from "../../../entities/Slot";
import { StepType } from "../../../entities/Step";
import { goBackToStorage } from "../tasks/GoBackToStorage";
import { setTaskOptimalsByJobAndStartOptimals } from "../creation/CalculateOptimals";

const MAXIMUM_SLOT_LENGTH_IN_MINUTES = 12 * 60;

// add this validations in the interactor
// const jwtScret = config.get("jwt_secret");
// const slotPayload = jwt.verify(slotId!, jwtScret) as SlotPayloadJwt;

// if (!slotPayload) {
//   throw new InvariantViolation("INVALID_SLOT_ID_FOR_RESCHEDULE", { jobId: job.getId() });
// }
// here you are going to receive all the information
export const rescheduleJobV2 = async (
  job: Job,
  newFrom: Date,
  newTo: Date,
  jobQuote: JobQuote,
  dynamicParameterValue: DynamicParameterValue,
  backToStorage: boolean,
  origin = JobEventSource.OPERATIONS,
  reusablePackages = false,
  reason?: string,
  metadata?: Record<string, any>,
  startTimeByTask?: StartTimeByTask,
): Promise<JobWithEvents> => {
  validateDates(newFrom, newTo, job.getId());
  const slotLength = validateSlotLength(newFrom, newTo, job.getId());
  updateSlot(job, newFrom, newTo);

  const event = buildRescheduleEvent(job, origin, reason, metadata);

  const { rescheduleOffsetPercentage } = dynamicParameterValue;
  const targetDate = moment(newFrom).add(Math.ceil((slotLength * rescheduleOffsetPercentage) / 100), "minutes");

  if(startTimeByTask) {
    setTaskOptimalsByJobAndStartOptimals(job, startTimeByTask);
    // create this method in src/use_cases/jobs/creation/CalculateOptimals.ts
    // export const setTaskOptimalsByJobAndStartOptimals = (job: Job, startTimeByTask: StartTimeByTask): void => {
    //   for (const task of job.getTasks()) {
    //     const taskType = task.getType();
    //     const departments = task.getDepartments();
    //     let currentTime: Date;
    //     const timeDetailsByTask: TimeDetailsByTask =
    //       startTimeByTask[getCapabilitiesInfoKey(taskType, departments) as TaskType]!;
    //     currentTime = moment.utc(timeDetailsByTask.startDate).toDate();
    //     task.getSteps().forEach((step, index) => {
    //       step.setOptimalStartTime(currentTime);
    //       const duration = timeDetailsByTask.stepsDuration[index];
    //       currentTime = moment.utc(currentTime).add(duration, "minutes").toDate();
    //       step.setOptimalEndTime(currentTime);
    //     });
    //   }
    // }
  }else{
    // you have to revert the changes made in this function
    await rescheduleJob(job, jobQuote, dynamicParameterValue, targetDate);
  }

  const resetDeliveryEvents: Event[] = [];
  if (backToStorage) {
    const { triggeredEvents } = goBackToStorage(job, reusablePackages);
    resetDeliveryEvents.push(...triggeredEvents);
  }

  return {
    associatedJob: job,
    triggeredEvents: [event, ...resetDeliveryEvents],
  };
};

const rescheduleJob = async (
  job: Job,
  jobQuote: JobQuote,
  parameterValue: DynamicParameterValue,
  target: Moment
): Promise<void> => {
  switch (job.getSlot().getOpsModel()) {
    case OperationalModel.FULL_SERVICE:
      await rescheduleFullserviceJob(job, jobQuote, parameterValue, target, startTimeByTask);
      break;
    case OperationalModel.PICK_AND_DELIVERY:
      await reschedulePickAndDeliveryJob(job, jobQuote, parameterValue, target, startTimeByTask);
      break;
    case OperationalModel.PICK_AND_COLLECT:
      await reschedulePickAndCollectJob(job, jobQuote, parameterValue, target, startTimeByTask);
      break;
    case OperationalModel.PICK_AND_COLLECT_NO_TRANSFER:
      await reschedulePickAndCollectNoTransferJob(job, jobQuote, parameterValue, target, startTimeByTask);
      break;
    case OperationalModel.PICK_AND_DELIVERY_WITH_STORAGE:
      await reschedulePickAndDeliveryWithStorageJob(job, jobQuote, parameterValue, target, startTimeByTask);
      break;
    case OperationalModel.PICK_AND_DELIVERY_WITH_STORAGE_NO_TRANSFER:
      await reschedulePickAndDeliveryWithStorageNoTransferJob(job, jobQuote, parameterValue, target, startTimeByTask);
      break;
    case OperationalModel.ZONE_PICKING_AND_COLLECT:
      await rescheduleZonePickingAndCollectJob(job, jobQuote, parameterValue, target, startTimeByTask);
      break;
    case OperationalModel.ZONE_PICKING_AND_DELIVERY_WITH_STORAGE:
      await rescheduleZonePickingAndDeliveryWithStorageJob(job, jobQuote, parameterValue, target, startTimeByTask);
      break;
    default:
      throw new InvariantViolation("OPERATIONAL_MODEL_NOT_SUPPORTED_FOR_RESCHEDULE", { jobId: job.getId() });
  }

  try {
    job.getDeliveryTask().setRoute(null);
  } catch (error) {
    return;
  }
};

const rescheduleFullserviceJob = async (
  job: Job,
  jobQuote: JobQuote,
  parameterValue: DynamicParameterValue,
  target: Moment,
): Promise<void> => {
  const fullServiceTask = job.getTaskByType(TaskType.FULL_SERVICE);
  await rescheduleTask(fullServiceTask, jobQuote, parameterValue, target);
};

const reschedulePickAndDeliveryJob = async (
  job: Job,
  jobQuote: JobQuote,
  parameterValue: DynamicParameterValue,
  target: Moment,
  startTimeByTask?: StartTimeByTask
): Promise<void> => {
  const deliveryTask = job.getTaskByType(TaskType.DELIVERY);
  const pickingTask = job.getTaskByType(TaskType.PICKING);
  if (startTimeByTask) {
    job.getTasks().forEach((task) => {
      rescheduleTaskWithSlotId(task, startTimeByTask);
    })
  } else {
    await rescheduleTask(deliveryTask, jobQuote, parameterValue, target);

    const transferStep = deliveryTask.getStepOfType(deliveryTask.getTransferStepType());
    await rescheduleTask(pickingTask, jobQuote, parameterValue, moment.utc(transferStep.getOptimalEndTime()));
  }
};

const reschedulePickAndCollectJob = async (
  job: Job,
  jobQuote: JobQuote,
  parameterValue: DynamicParameterValue,
  target: Moment | null,
  startTimeByTask?: StartTimeByTask
): Promise<void> => {
  const pickUpTask = job.getTaskByType(TaskType.PICK_UP);
  const storageTask = job.getTaskByType(TaskType.STORAGE);
  const pickingWithStorageTask = job.getTaskByType(TaskType.PICKING_WITH_STORAGE);

  if (startTimeByTask) {
    rescheduleTaskWithSlotId(pickUpTask, startTimeByTask);
    rescheduleTaskWithSlotId(storageTask, startTimeByTask);
    rescheduleTaskWithSlotId(pickingWithStorageTask, startTimeByTask);
  } else {
    await rescheduleTask(pickUpTask, jobQuote, parameterValue, target!);

    const deliveringStep = pickUpTask.getStepOfType(StepType.DELIVERING);
    await rescheduleTask(storageTask, jobQuote, parameterValue, moment.utc(deliveringStep.getOptimalStartTime()));

    const transferStep = storageTask.getStepOfType(storageTask.getTransferStepType());
    await rescheduleTask(
      pickingWithStorageTask,
      jobQuote,
      parameterValue,
      moment.utc(transferStep.getOptimalEndTime())
    );
  }
};

const reschedulePickAndCollectNoTransferJob = async (
  job: Job,
  jobQuote: JobQuote,
  parameterValue: DynamicParameterValue,
  target: Moment | null,
  startTimeByTask?: StartTimeByTask
): Promise<void> => {
  const pickUpTask = job.getTaskByType(TaskType.PICK_UP);
  const pickAndStorageTask = job.getTaskByType(TaskType.PICKING_AND_STORAGE);
  if (startTimeByTask) {
    rescheduleTaskWithSlotId(pickUpTask, startTimeByTask);
    rescheduleTaskWithSlotId(pickAndStorageTask, startTimeByTask);
  } else {
    await rescheduleTask(pickUpTask, jobQuote, parameterValue, target!);

    const deliveringStep = pickUpTask.getStepOfType(StepType.DELIVERING);
    await rescheduleTask(
      pickAndStorageTask,
      jobQuote,
      parameterValue,
      moment.utc(deliveringStep.getOptimalStartTime())
    );
  }
};

const reschedulePickAndDeliveryWithStorageJob = async (
  job: Job,
  jobQuote: JobQuote,
  parameterValue: DynamicParameterValue,
  target: Moment | null,
  startTimeByTask?: StartTimeByTask
): Promise<void> => {
  const deliveryWithStorageTask = job.getTaskByType(TaskType.DELIVERY_WITH_STORAGE);
  const pickUpForDeliveryTask = job.getTaskByType(TaskType.PICK_UP_FOR_DELIVERY);
  const storageTask = job.getTaskByType(TaskType.STORAGE);
  const pickingWithStorageTask = job.getTaskByType(TaskType.PICKING_WITH_STORAGE);

  if (startTimeByTask) {
    rescheduleTaskWithSlotId(deliveryWithStorageTask, startTimeByTask);
    rescheduleTaskWithSlotId(pickUpForDeliveryTask, startTimeByTask);
    rescheduleTaskWithSlotId(storageTask, startTimeByTask);
    rescheduleTaskWithSlotId(pickingWithStorageTask, startTimeByTask);
  } else {
    await rescheduleTask(deliveryWithStorageTask, jobQuote, parameterValue, target!);

    const secondTransferStep = deliveryWithStorageTask.getStepOfType(deliveryWithStorageTask.getTransferStepType());
    await rescheduleTask(
      pickUpForDeliveryTask,
      jobQuote,
      parameterValue,
      moment.utc(secondTransferStep.getOptimalEndTime())
    );

    await rescheduleTask(storageTask, jobQuote, parameterValue, moment.utc(secondTransferStep.getOptimalStartTime()));

    const firstTransferStep = storageTask.getStepOfType(storageTask.getTransferStepType());
    await rescheduleTask(
      pickingWithStorageTask,
      jobQuote,
      parameterValue,
      moment.utc(firstTransferStep.getOptimalEndTime())
    );
  }
};

const reschedulePickAndDeliveryWithStorageNoTransferJob = async (
  job: Job,
  jobQuote: JobQuote,
  parameterValue: DynamicParameterValue,
  target: Moment | null,
  startTimeByTask?: StartTimeByTask
): Promise<void> => {
  const deliveryWithStorageTask = job.getTaskByType(TaskType.DELIVERY_WITH_STORAGE);
  const pickUpForDeliveryTask = job.getTaskByType(TaskType.PICK_UP_FOR_DELIVERY);
  const pickingAndStorageTask = job.getTaskByType(TaskType.PICKING_AND_STORAGE);

  if (startTimeByTask) {
    rescheduleTaskWithSlotId(deliveryWithStorageTask, startTimeByTask);
    rescheduleTaskWithSlotId(pickUpForDeliveryTask, startTimeByTask);
    rescheduleTaskWithSlotId(pickingAndStorageTask, startTimeByTask);
  } else {
    await rescheduleTask(deliveryWithStorageTask, jobQuote, parameterValue, target!);

    const transferStep = deliveryWithStorageTask.getStepOfType(deliveryWithStorageTask.getTransferStepType());
    await rescheduleTask(pickUpForDeliveryTask, jobQuote, parameterValue, moment.utc(transferStep.getOptimalEndTime()));

    await rescheduleTask(
      pickingAndStorageTask,
      jobQuote,
      parameterValue,
      moment.utc(transferStep.getOptimalStartTime())
    );
  }
};

const rescheduleZonePickingAndCollectJob = async (
  job: Job,
  jobQuote: JobQuote,
  parameterValue: DynamicParameterValue,
  target: Moment | null,
  startTimeByTask?: StartTimeByTask
): Promise<void> => {
  const pickUpTask = job.getTaskByType(TaskType.PICK_UP);
  const consolidationTask = job.getTaskByType(TaskType.CONSOLIDATION);
  const zonePickingTasks = job.getTasksByType(TaskType.ZONE_PICKING);

  if (startTimeByTask) {
    rescheduleTaskWithSlotId(pickUpTask, startTimeByTask);
    rescheduleTaskWithSlotId(consolidationTask, startTimeByTask);
    zonePickingTasks.forEach((zonePickingTask) => {
      return rescheduleTaskWithSlotId(zonePickingTask, startTimeByTask);
    });
  } else {
    await rescheduleTask(pickUpTask, jobQuote, parameterValue, target!);

    await rescheduleTask(consolidationTask, jobQuote, parameterValue, moment.utc(pickUpTask.getOptimalStartTime()));

    await Promise.all(
      zonePickingTasks.map(async (zonePickingTask: Task) => {
        return rescheduleTask(
          zonePickingTask,
          jobQuote,
          parameterValue,
          moment.utc(consolidationTask.getOptimalStartTime())
        );
      })
    );
  }
};

const rescheduleZonePickingAndDeliveryWithStorageJob = async (
  job: Job,
  jobQuote: JobQuote,
  parameterValue: DynamicParameterValue,
  target: Moment | null,
  startTimeByTask?: StartTimeByTask
): Promise<void> => {
  const deliveryWithStorageTask = job.getTaskByType(TaskType.DELIVERY_WITH_STORAGE);
  const pickUpForDeliveryTask = job.getTaskByType(TaskType.PICK_UP_FOR_DELIVERY);
  const consolidationTask = job.getTaskByType(TaskType.CONSOLIDATION);
  const zonePickingTasks = job.getTasksByType(TaskType.ZONE_PICKING);

  if (startTimeByTask) {
    setTaskOptimalsByJobAndStartOptimals(job, startTimeByTask);
  } else {
    await rescheduleTask(deliveryWithStorageTask, jobQuote, parameterValue, target!);

    const transferStep = deliveryWithStorageTask.getStepOfType(deliveryWithStorageTask.getTransferStepType());
    await rescheduleTask(pickUpForDeliveryTask, jobQuote, parameterValue, moment.utc(transferStep.getOptimalEndTime()));

    await rescheduleTask(
      consolidationTask,
      jobQuote,
      parameterValue,
      moment.utc(pickUpForDeliveryTask.getOptimalStartTime())
    );

    await Promise.all(
      zonePickingTasks.map(async (zonePickingTask: Task) => {
        return rescheduleTask(
          zonePickingTask,
          jobQuote,
          parameterValue,
          moment.utc(consolidationTask.getOptimalStartTime())
        );
      })
    );
  }
};

const rescheduleTask = async (
  task: Task,
  jobQuote: JobQuote,
  parameterValue: DynamicParameterValue,
  endTime: Moment
): Promise<void> => {
  let optimalStartTime: Moment;
  let optimalEndTime = moment.utc(endTime);
  const steps = task.getSteps();
  for (let i = steps.length - 1; i >= 0; i--) {
    const step = steps[i];
    const stepDurationInMinutes = await getEstimatedStepDurationInMinutes(
      step.getType(),
      jobQuote,
      optimalEndTime,
      parameterValue
    );

    optimalStartTime = moment.utc(optimalEndTime).subtract(stepDurationInMinutes, "minutes");
    step.setOptimalStartTime(optimalStartTime.toDate());
    step.setOptimalEndTime(optimalEndTime.toDate());
    optimalEndTime = optimalStartTime;
  }
};

const validateDates = (from: Date, to: Date, jobId: string): void => {
  if (moment(to).isBefore(moment(from))) {
    throw new InvariantViolation("INVALID_SLOT_FOR_RESCHEDULE", { jobId });
  }
};

const validateSlotLength = (from: Date, to: Date, jobId: string): number => {
  const slotLength = getAbsoluteDifferenceInMinutes(from, to);
  if (slotLength > MAXIMUM_SLOT_LENGTH_IN_MINUTES) {
    throw new InvariantViolation("INVALID_SLOT_LENGTH_FOR_RESCHEDULE", { jobId });
  }

  return slotLength;
};

const updateSlot = (job: Job, from: Date, to: Date): void => {
  job.getSlot().setFrom(from);
  job.getSlot().setTo(to);
};

const buildRescheduleEvent = (
  job: Job,
  origin: JobEventSource,
  reason?: string,
  metadata?: Record<string, any>
): Event => {
  return new Event(job.getId(), EventType.RESCHEDULED, job.getClientId(), {
    taskIds: job.getTasks().map((task) => {
      return task.getId();
    }),
    origin,
    reason,
    metadata,
  });
};
Editor is loading...
Leave a Comment