-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(shipping): add calculate service and controller
feat(shipping): add create controller feat(shipping): remove get controller feat(shipping): add rates controller feat(shipping): add calculate-cost service feat(shipping): add calculate-cost tests feat(shipping): add calculate service feat(shipping): add calculate tests The changes in this commit include: - Added a new calculate service and controller for shipping calculations. - Added a new create controller for shipping. - Removed the get controller for shipping. - Added a new rates controller for shipping. - Added a new calculate-cost service for shipping calculations. - Added tests for the calculate-cost service. - Added a new calculate service for shipping calculations. - Added tests for the calculate service. feat(shipping): add create service and tests feat(shipping): add get service and tests feat(shipping): add rates service and tests feat(shipping): add shipping model feat(shipping): add shipping schema and types - Add shipping.schema.ts file to define the mongoose schema for the shipping model. - Define the IShipping and IShippingDocument interfaces to represent the shape of the shipping document in the database. - Create the ShippingMongooseSchema using mongoose.Schema to define the fields and their types for the shipping model. - Add the necessary fields and their types to the ShippingMongooseSchema. - Add timestamps to the ShippingMongooseSchema to automatically track the creation and update timestamps. refactor(shipping): remove shipping service and types - Remove the shipping.service.ts file as it is no longer needed. - Remove the shipping.types.ts file as it is no longer needed.
- Loading branch information
1 parent
1667b9c
commit de590ce
Showing
18 changed files
with
679 additions
and
275 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { _ } from "~/library/handler"; | ||
|
||
import { z } from "zod"; | ||
import { NumberOrStringType, StringOrObjectIdType } from "~/library/zod"; | ||
import { ShippingServiceCreate } from "../services/create"; | ||
import { ShippingZodSchema } from "../shipping.types"; | ||
|
||
export type ShippingControllerCreateRequest = { | ||
body: z.infer<typeof ShippingControllerCreateSchema>; | ||
}; | ||
|
||
export const ShippingControllerCreateSchema = z | ||
.object({ | ||
locationId: StringOrObjectIdType, | ||
customerId: NumberOrStringType, | ||
}) | ||
.merge(ShippingZodSchema.pick({ destination: true })); | ||
|
||
export type ShippingControllerCreateResponse = Awaited< | ||
ReturnType<typeof ShippingServiceCreate> | ||
>; | ||
|
||
export const ShippingControllerCreate = _( | ||
async ({ body }: ShippingControllerCreateRequest) => { | ||
const validateData = ShippingControllerCreateSchema.parse(body); | ||
return ShippingServiceCreate(validateData); | ||
} | ||
); |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export * from "./calculate"; | ||
export * from "./get"; | ||
export * from "./create"; | ||
export * from "./rates"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { _ } from "~/library/handler"; | ||
|
||
import { ShippingBody, ShippingServiceRates } from "../services/rates"; | ||
|
||
export type ShippingControllerRatesRequest = { | ||
body: ShippingBody; | ||
}; | ||
|
||
export type ShippingControllerRatesResponse = Awaited< | ||
ReturnType<typeof ShippingServiceRates> | ||
>; | ||
|
||
export const ShippingControllerRates = _( | ||
async ({ body }: ShippingControllerRatesRequest) => { | ||
return ShippingServiceRates(body); | ||
} | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import { faker } from "@faker-js/faker"; | ||
import { getLocationObject } from "~/library/jest/helpers/location"; | ||
import { Shipping } from "../shipping.types"; | ||
import { ShippingServiceCalculateCost } from "./calculate-cost"; | ||
|
||
require("~/library/jest/mongoose/mongodb.jest"); | ||
|
||
jest.mock("~/functions/location", () => ({ | ||
LocationServiceLookup: jest.fn(), | ||
})); | ||
|
||
describe("ShippingServiceCalculateCost", () => { | ||
it("should correctly calculate the cost", () => { | ||
const shipping: Omit<Shipping, "_id" | "cost" | "location"> = { | ||
origin: getLocationObject({ | ||
distanceHourlyRate: 100, | ||
fixedRatePerKm: 20, | ||
distanceForFree: 5, | ||
startFee: 0, | ||
}), | ||
destination: { | ||
name: faker.person.firstName(), | ||
fullAddress: faker.location.streetAddress(), | ||
}, | ||
duration: { | ||
text: "1 hour", | ||
value: 60, | ||
}, | ||
distance: { text: "5.3 km", value: 5.3 }, | ||
}; | ||
|
||
const actualCost = ShippingServiceCalculateCost(shipping); | ||
|
||
expect(actualCost).toEqual(106); | ||
}); | ||
|
||
it("should correctly calculate the cost with start fee", () => { | ||
const shipping: Omit<Shipping, "_id" | "cost" | "location"> = { | ||
origin: getLocationObject({ | ||
distanceHourlyRate: 100, | ||
fixedRatePerKm: 20, | ||
distanceForFree: 5, | ||
startFee: 400, | ||
}), | ||
destination: { | ||
name: faker.person.firstName(), | ||
fullAddress: faker.location.streetAddress(), | ||
}, | ||
duration: { | ||
text: "1 hour", | ||
value: 60, | ||
}, | ||
distance: { text: "5.3 km", value: 5.3 }, | ||
}; | ||
|
||
const actualCost = ShippingServiceCalculateCost(shipping); | ||
|
||
expect(actualCost).toEqual(506); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { Shipping } from "../shipping.types"; | ||
|
||
export const ShippingServiceCalculateCost = ({ | ||
duration: { value: duration }, | ||
distance: { value: distance }, | ||
origin, | ||
}: Omit<Shipping, "_id" | "location" | "destination" | "cost">) => { | ||
const { distanceForFree, fixedRatePerKm, distanceHourlyRate, startFee } = | ||
origin; | ||
|
||
// Calculate the chargeable distance. | ||
const chargeableDistance = Math.max(0, distance - distanceForFree); | ||
|
||
// Calculate the cost for the distance. | ||
const distanceCost = chargeableDistance * fixedRatePerKm; | ||
|
||
// Calculate the cost for the duration. | ||
const durationInHours = duration / 60; | ||
const durationCost = durationInHours * distanceHourlyRate; | ||
|
||
// Total cost is the sum of the cost for the distance, the duration, and start fee. | ||
const totalCost = distanceCost + durationCost + startFee; | ||
|
||
return Math.round(totalCost); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
import { LocationServiceGet } from "~/functions/location/services/get"; | ||
import { LocationServiceGetTravelTime } from "~/functions/location/services/get-travel-time"; | ||
import { createLocation } from "~/library/jest/helpers/location"; | ||
import { ShippingServiceCalculate } from "./calculate"; | ||
|
||
require("~/library/jest/mongoose/mongodb.jest"); | ||
|
||
jest.mock("~/functions/location/services/get-travel-time", () => ({ | ||
LocationServiceGetTravelTime: jest.fn(), | ||
})); | ||
|
||
jest.mock("~/functions/location/services/get", () => ({ | ||
LocationServiceGet: jest.fn(), | ||
})); | ||
|
||
describe("ShippingServiceCalculate", () => { | ||
it("should calculate destination from locationId", async () => { | ||
const location = await createLocation({ | ||
locationType: "destination" as any, | ||
customerId: 1, | ||
distanceHourlyRate: 200, | ||
fixedRatePerKm: 2, | ||
distanceForFree: 0, | ||
}); | ||
|
||
(LocationServiceGet as jest.Mock).mockResolvedValue(location); | ||
|
||
(LocationServiceGetTravelTime as jest.Mock).mockResolvedValue({ | ||
duration: { | ||
text: "120 min", | ||
value: 120, | ||
}, | ||
distance: { | ||
text: "100 km", | ||
value: 100, | ||
}, | ||
} as Awaited<ReturnType<typeof LocationServiceGetTravelTime>>); | ||
|
||
const response = await ShippingServiceCalculate({ | ||
locationId: location._id, | ||
destination: { | ||
name: "hotel a", | ||
fullAddress: "Dortesvej 17 1 th", | ||
}, | ||
}); | ||
|
||
expect(response).toEqual({ | ||
duration: { text: "120 min", value: 120 }, | ||
distance: { text: "100 km", value: 100 }, | ||
cost: { value: 600, currency: "DKK" }, | ||
}); | ||
}); | ||
|
||
it("should throw error since maxDriveDistance 50, and distance is 100", async () => { | ||
const location = await createLocation({ | ||
locationType: "destination" as any, | ||
customerId: 1, | ||
distanceHourlyRate: 200, | ||
fixedRatePerKm: 2, | ||
minDriveDistance: 20, | ||
maxDriveDistance: 50, | ||
distanceForFree: 0, | ||
}); | ||
|
||
(LocationServiceGet as jest.Mock).mockResolvedValue(location); | ||
|
||
(LocationServiceGetTravelTime as jest.Mock).mockResolvedValue({ | ||
duration: { | ||
text: "120 min", | ||
value: 120, | ||
}, | ||
distance: { | ||
text: "100 km", | ||
value: 100, | ||
}, | ||
} as Awaited<ReturnType<typeof LocationServiceGetTravelTime>>); | ||
|
||
await expect( | ||
ShippingServiceCalculate({ | ||
locationId: location._id, | ||
destination: { | ||
name: "hotel c", | ||
fullAddress: "Dortesvej 17 1 th", | ||
}, | ||
}) | ||
).rejects.toThrowError(); | ||
}); | ||
|
||
it("should throw error since minDriveDistance is 40, and distance is 30", async () => { | ||
const location = await createLocation({ | ||
locationType: "destination" as any, | ||
customerId: 1, | ||
distanceHourlyRate: 200, | ||
fixedRatePerKm: 2, | ||
minDriveDistance: 40, | ||
maxDriveDistance: 500, | ||
distanceForFree: 0, | ||
}); | ||
|
||
(LocationServiceGet as jest.Mock).mockResolvedValue(location); | ||
|
||
(LocationServiceGetTravelTime as jest.Mock).mockResolvedValue({ | ||
duration: { | ||
text: "120 min", | ||
value: 120, | ||
}, | ||
distance: { | ||
text: "100 km", | ||
value: 30, | ||
}, | ||
} as Awaited<ReturnType<typeof LocationServiceGetTravelTime>>); | ||
|
||
await expect( | ||
ShippingServiceCalculate({ | ||
locationId: location._id, | ||
destination: { | ||
name: "hotel c", | ||
fullAddress: "Dortesvej 17 1 th", | ||
}, | ||
}) | ||
).rejects.toThrowError(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { | ||
LocationServiceGet, | ||
LocationServiceGetProps, | ||
} from "~/functions/location/services/get"; | ||
import { LocationServiceGetTravelTime } from "~/functions/location/services/get-travel-time"; | ||
import { NotFoundError } from "~/library/handler"; | ||
import { Shipping } from "../shipping.types"; | ||
import { ShippingServiceCalculateCost } from "./calculate-cost"; | ||
|
||
export const ShippingServiceCalculate = async ( | ||
props: Required<LocationServiceGetProps & Pick<Shipping, "destination">> | ||
) => { | ||
const location = await LocationServiceGet(props); | ||
const { destination } = props; | ||
|
||
if (!destination) { | ||
throw new NotFoundError([ | ||
{ | ||
code: "custom", | ||
message: "DESTINATION_MISSING", | ||
path: ["destination"], | ||
}, | ||
]); | ||
} | ||
|
||
const travelTime = await LocationServiceGetTravelTime({ | ||
origin: location.fullAddress, | ||
destination: destination.fullAddress, | ||
}); | ||
|
||
if (location.minDriveDistance > travelTime.distance.value) { | ||
throw new NotFoundError([ | ||
{ | ||
code: "custom", | ||
message: "MIN_DRIVE_DISTANCE_EXCEEDED", | ||
path: ["minDriveDistance"], | ||
}, | ||
]); | ||
} | ||
|
||
if (location.maxDriveDistance < travelTime.distance.value) { | ||
throw new NotFoundError([ | ||
{ | ||
code: "custom", | ||
message: "MAX_DRIVE_DISTANCE_EXCEEDED", | ||
path: ["maxDriveDistance"], | ||
}, | ||
]); | ||
} | ||
|
||
const cost = ShippingServiceCalculateCost({ | ||
...travelTime, | ||
origin: location, | ||
}); | ||
|
||
return { | ||
...travelTime, | ||
cost: { | ||
value: cost, | ||
currency: "DKK", | ||
}, | ||
}; | ||
}; |
Oops, something went wrong.